blob: af88656e4f7e97974f3a22e310a1b1a0ed2dbc1d [file] [edit]
// 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