| // 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/tracer.h" | |
| #include <cstdint> | |
| #include <memory> | |
| #include <string> | |
| #include <thread> // NOLINT(build/c++11) | |
| #include <vector> | |
| #include "absl/base/nullability.h" | |
| #include "absl/status/status.h" | |
| #include "absl/strings/string_view.h" | |
| #include "async/async_context.h" | |
| #include "gmock/gmock.h" | |
| #include "gtest/gtest.h" | |
| #include "tracing/noop_tracing.h" | |
| #include "tracing/scoped_span.h" | |
| #include "tracing/scoped_tracer_factory.h" | |
| #include "tracing/span.h" | |
| #include "tracing/thread_context.h" | |
| #include "tracing/tracer_factory.h" | |
| namespace credentio_tracing { | |
| namespace { | |
| using ::testing::_; | |
| using ::testing::Eq; | |
| using ::testing::Return; | |
| using ::testing::StrictMock; | |
| class MockTracer : public Tracer { | |
| public: | |
| MOCK_METHOD(std::unique_ptr<Span>, StartSpanImpl, | |
| (absl::string_view span_name, const StartSpanOptions& options), | |
| (const, override)); | |
| }; | |
| class MockTracerFactory : public TracerFactory { | |
| public: | |
| MOCK_METHOD(absl_nonnull std::shared_ptr<Tracer>, GetTracer, | |
| (absl::string_view tracer_name), (const, override)); | |
| }; | |
| TEST(StartSpanOptionsTest, DefaultOptions) { | |
| StartSpanOptions options; | |
| EXPECT_EQ(options.parent, nullptr); | |
| EXPECT_TRUE(options.attributes.empty()); | |
| } | |
| TEST(StartSpanOptionsTest, CustomOptions) { | |
| NoopSpan parent_span; | |
| StartSpanOptions options; | |
| options.parent = &parent_span; | |
| options.attributes.push_back({"key1", std::string("val1")}); | |
| options.attributes.push_back({"key2", int64_t{100}}); | |
| EXPECT_EQ(options.parent, &parent_span); | |
| ASSERT_EQ(options.attributes.size(), 2); | |
| EXPECT_EQ(options.attributes[0].first, "key1"); | |
| EXPECT_EQ(std::get<std::string>(options.attributes[0].second), "val1"); | |
| EXPECT_EQ(options.attributes[1].first, "key2"); | |
| EXPECT_EQ(std::get<int64_t>(options.attributes[1].second), 100); | |
| } | |
| TEST(TracerTest, StartSpanInvocation) { | |
| StrictMock<MockTracer> mock_tracer; | |
| EXPECT_CALL(mock_tracer, StartSpanImpl(Eq("test_span"), _)) | |
| .WillOnce([](absl::string_view, const StartSpanOptions& options) { | |
| EXPECT_EQ(options.parent, nullptr); | |
| return std::make_unique<NoopSpan>(); | |
| }); | |
| std::unique_ptr<Span> span = mock_tracer.StartSpan("test_span", {}); | |
| EXPECT_NE(span, nullptr); | |
| } | |
| TEST(TracerTest, StartSpanSingleArgumentOverload) { | |
| StrictMock<MockTracer> mock_tracer; | |
| EXPECT_CALL(mock_tracer, StartSpanImpl(Eq("simple_span"), _)) | |
| .WillOnce([](absl::string_view, const StartSpanOptions& options) { | |
| EXPECT_EQ(options.parent, nullptr); | |
| EXPECT_TRUE(options.attributes.empty()); | |
| return std::make_unique<NoopSpan>(); | |
| }); | |
| std::unique_ptr<Span> span = mock_tracer.StartSpan("simple_span"); | |
| EXPECT_NE(span, nullptr); | |
| } | |
| TEST(TracerTest, StartSpanImplicitParentFromThreadContext) { | |
| NoopSpan active_thread_span; | |
| ThreadContext::SetCurrentSpan(&active_thread_span); | |
| StrictMock<MockTracer> mock_tracer; | |
| EXPECT_CALL(mock_tracer, StartSpanImpl(Eq("child_span"), _)) | |
| .WillOnce([&active_thread_span](absl::string_view, | |
| const StartSpanOptions& options) { | |
| EXPECT_EQ(options.parent, &active_thread_span); | |
| return std::make_unique<NoopSpan>(); | |
| }); | |
| std::unique_ptr<Span> span = mock_tracer.StartSpan("child_span"); | |
| EXPECT_NE(span, nullptr); | |
| ThreadContext::SetCurrentSpan(nullptr); | |
| } | |
| TEST(TracerTest, StartSpanExplicitParentOverridesThreadContext) { | |
| NoopSpan active_thread_span; | |
| NoopSpan explicit_parent_span; | |
| ThreadContext::SetCurrentSpan(&active_thread_span); | |
| StrictMock<MockTracer> mock_tracer; | |
| StartSpanOptions options; | |
| options.parent = &explicit_parent_span; | |
| EXPECT_CALL(mock_tracer, StartSpanImpl(Eq("child_span"), _)) | |
| .WillOnce([&explicit_parent_span](absl::string_view, | |
| const StartSpanOptions& options) { | |
| EXPECT_EQ(options.parent, &explicit_parent_span); | |
| return std::make_unique<NoopSpan>(); | |
| }); | |
| std::unique_ptr<Span> span = mock_tracer.StartSpan("child_span", options); | |
| EXPECT_NE(span, nullptr); | |
| ThreadContext::SetCurrentSpan(nullptr); | |
| } | |
| TEST(TracerFactoryTest, GetTracerInvocation) { | |
| StrictMock<MockTracerFactory> mock_factory; | |
| auto mock_tracer = std::make_shared<StrictMock<MockTracer>>(); | |
| EXPECT_CALL(mock_factory, GetTracer(Eq("mock_tracer"))) | |
| .WillOnce(Return(mock_tracer)); | |
| std::shared_ptr<Tracer> tracer = mock_factory.GetTracer("mock_tracer"); | |
| EXPECT_EQ(tracer, mock_tracer); | |
| } | |
| TEST(TracerFactoryTest, DefaultGlobalFactoryIsNoopTracerFactory) { | |
| auto factory = TracerFactory::GetGlobalFactory(); | |
| ASSERT_NE(factory, nullptr); | |
| auto tracer = factory->GetTracer("mock_tracer"); | |
| ASSERT_NE(tracer, nullptr); | |
| auto span = tracer->StartSpan("test_span", {}); | |
| ASSERT_NE(span, nullptr); | |
| } | |
| TEST(TracerFactoryTest, GlobalFactoryRegistration) { | |
| auto initial_factory = TracerFactory::GetGlobalFactory(); | |
| EXPECT_NE(initial_factory, nullptr); | |
| auto custom_factory = std::make_shared<StrictMock<MockTracerFactory>>(); | |
| TracerFactory::RegisterGlobalFactory(custom_factory); | |
| EXPECT_EQ(TracerFactory::GetGlobalFactory(), custom_factory); | |
| // Restore original factory state after test | |
| TracerFactory::RegisterGlobalFactory(initial_factory); | |
| } | |
| TEST(TracerFactoryTest, RegisterNullptrResetsToNoopTracerFactory) { | |
| auto initial_factory = TracerFactory::GetGlobalFactory(); | |
| EXPECT_NE(initial_factory, nullptr); | |
| auto custom_factory = std::make_shared<StrictMock<MockTracerFactory>>(); | |
| TracerFactory::RegisterGlobalFactory(custom_factory); | |
| EXPECT_EQ(TracerFactory::GetGlobalFactory(), custom_factory); | |
| TracerFactory::RegisterGlobalFactory(nullptr); | |
| auto current_factory = TracerFactory::GetGlobalFactory(); | |
| ASSERT_NE(current_factory, nullptr); | |
| EXPECT_NE(current_factory, custom_factory); | |
| // Restore original factory state after test | |
| TracerFactory::RegisterGlobalFactory(initial_factory); | |
| } | |
| TEST(TracerFactoryTest, ConcurrentGlobalFactoryAccess) { | |
| constexpr int kNumThreads = 8; | |
| constexpr int kIterations = 100; | |
| std::vector<std::thread> threads; | |
| threads.reserve(kNumThreads); | |
| for (int i = 0; i < kNumThreads; ++i) { | |
| threads.emplace_back([&]() { | |
| for (int j = 0; j < kIterations; ++j) { | |
| auto factory = TracerFactory::GetGlobalFactory(); | |
| EXPECT_NE(factory, nullptr); | |
| auto tracer = factory->GetTracer("concurrent_test"); | |
| EXPECT_NE(tracer, nullptr); | |
| auto span = tracer->StartSpan("concurrent_span", {}); | |
| EXPECT_NE(span, nullptr); | |
| } | |
| }); | |
| } | |
| for (auto& t : threads) { | |
| t.join(); | |
| } | |
| } | |
| TEST(TracerFactoryResolutionTest, FallsBackToGlobalFactory) { | |
| auto initial_factory = TracerFactory::GetGlobalFactory(); | |
| auto global_factory = std::make_shared<StrictMock<MockTracerFactory>>(); | |
| TracerFactory::RegisterGlobalFactory(global_factory); | |
| EXPECT_EQ(TracerFactory::Get(), global_factory); | |
| EXPECT_EQ(ResolveTracerFactory(credentio::AsyncContext()), global_factory); | |
| TracerFactory::RegisterGlobalFactory(initial_factory); | |
| } | |
| TEST(TracerFactoryResolutionTest, ThreadLocalFactoryShadowsGlobal) { | |
| auto initial_factory = TracerFactory::GetGlobalFactory(); | |
| auto global_factory = std::make_shared<StrictMock<MockTracerFactory>>(); | |
| auto thread_local_factory = std::make_shared<StrictMock<MockTracerFactory>>(); | |
| TracerFactory::RegisterGlobalFactory(global_factory); | |
| { | |
| ScopedTracerFactory guard(thread_local_factory); | |
| EXPECT_EQ(TracerFactory::Get(), thread_local_factory); | |
| EXPECT_EQ(ResolveTracerFactory(credentio::AsyncContext()), | |
| thread_local_factory); | |
| } | |
| EXPECT_EQ(TracerFactory::GetThreadLocalFactory(), nullptr); | |
| EXPECT_EQ(TracerFactory::Get(), global_factory); | |
| TracerFactory::RegisterGlobalFactory(initial_factory); | |
| } | |
| TEST(TracerFactoryResolutionTest, ContextFactoryShadowsThreadLocalAndGlobal) { | |
| auto initial_factory = TracerFactory::GetGlobalFactory(); | |
| auto global_factory = std::make_shared<StrictMock<MockTracerFactory>>(); | |
| auto thread_local_factory = std::make_shared<StrictMock<MockTracerFactory>>(); | |
| auto context_factory = std::make_shared<StrictMock<MockTracerFactory>>(); | |
| TracerFactory::RegisterGlobalFactory(global_factory); | |
| ScopedTracerFactory guard(thread_local_factory); | |
| credentio::AsyncContext context; | |
| context.SetExtension<TracerFactory>(context_factory); | |
| EXPECT_EQ(ResolveTracerFactory(context), context_factory); | |
| TracerFactory::RegisterGlobalFactory(initial_factory); | |
| } | |
| TEST(TracerFactoryResolutionTest, ScopedTracerFactoryRestoresPrevious) { | |
| auto outer = std::make_shared<StrictMock<MockTracerFactory>>(); | |
| auto inner = std::make_shared<StrictMock<MockTracerFactory>>(); | |
| { | |
| ScopedTracerFactory outer_guard(outer); | |
| EXPECT_EQ(TracerFactory::GetThreadLocalFactory(), outer); | |
| { | |
| ScopedTracerFactory inner_guard(inner); | |
| EXPECT_EQ(TracerFactory::GetThreadLocalFactory(), inner); | |
| } | |
| EXPECT_EQ(TracerFactory::GetThreadLocalFactory(), outer); | |
| } | |
| EXPECT_EQ(TracerFactory::GetThreadLocalFactory(), nullptr); | |
| } | |
| TEST(TracerFactoryResolutionTest, ThreadLocalFactoryDoesNotLeakAcrossThreads) { | |
| auto initial_factory = TracerFactory::GetGlobalFactory(); | |
| auto global_factory = std::make_shared<StrictMock<MockTracerFactory>>(); | |
| auto thread_local_factory = std::make_shared<StrictMock<MockTracerFactory>>(); | |
| TracerFactory::RegisterGlobalFactory(global_factory); | |
| ScopedTracerFactory guard(thread_local_factory); | |
| // A thread-local factory covers only the installing thread, which is why | |
| // asynchronous work needs the factory on the context instead. | |
| std::shared_ptr<TracerFactory> seen_without_context; | |
| std::shared_ptr<TracerFactory> seen_with_context; | |
| auto context_factory = std::make_shared<StrictMock<MockTracerFactory>>(); | |
| credentio::AsyncContext context; | |
| context.SetExtension<TracerFactory>(context_factory); | |
| std::thread worker([&]() { | |
| seen_without_context = TracerFactory::Get(); | |
| seen_with_context = ResolveTracerFactory(context); | |
| }); | |
| worker.join(); | |
| EXPECT_EQ(seen_without_context, global_factory); | |
| EXPECT_EQ(seen_with_context, context_factory); | |
| TracerFactory::RegisterGlobalFactory(initial_factory); | |
| } | |
| TEST(NoopSpanTest, AllMethodsExecuteWithoutError) { | |
| NoopSpan span; | |
| span.SetAttribute("key_str", std::string("value")); | |
| span.SetAttribute("key_int", int64_t{42}); | |
| span.SetAttribute("key_double", 3.14); | |
| span.SetAttribute("key_bool", true); | |
| span.AddEvent("test_event"); | |
| span.SetStatus(absl::OkStatus()); | |
| span.SetStatus(absl::InvalidArgumentError("invalid argument")); | |
| span.End(); | |
| } | |
| TEST(NoopTracerTest, StartSpanReturnsNonNullNoopSpan) { | |
| NoopTracer tracer; | |
| StartSpanOptions options; | |
| std::unique_ptr<Span> span = tracer.StartSpan("test_span", options); | |
| ASSERT_NE(span, nullptr); | |
| span->SetAttribute("key", int64_t{100}); | |
| span->AddEvent("event"); | |
| span->SetStatus(absl::OkStatus()); | |
| span->End(); | |
| } | |
| TEST(NoopTracerTest, IntegrationWithScopedSpan) { | |
| NoopTracer tracer; | |
| { | |
| ScopedSpan scoped_span(tracer.StartSpan("scoped_op", {})); | |
| ASSERT_NE(scoped_span.get(), nullptr); | |
| scoped_span->SetAttribute("attr", std::string("val")); | |
| scoped_span.SetStatus(absl::OkStatus()); | |
| } | |
| } | |
| } // namespace | |
| } // namespace credentio_tracing |