blob: 0d4898b9e0078adeaa9be2aa2f65f5de7a642089 [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/scoped_span.h"
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include "absl/status/status.h"
#include "absl/strings/string_view.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "tracing/span.h"
#include "tracing/thread_context.h"
namespace credentio_tracing {
namespace {
using ::testing::InSequence;
using ::testing::StrictMock;
class MockSpan : public Span {
public:
MOCK_METHOD(void, SetAttribute, (absl::string_view key, AttributeValue value),
(override));
MOCK_METHOD(void, AddEvent, (absl::string_view name), (override));
MOCK_METHOD(void, SetStatus, (const absl::Status& status), (override));
MOCK_METHOD(void, End, (), (override));
};
TEST(ScopedSpanTest, CallsEndOnDestruction) {
auto mock_span = std::make_unique<StrictMock<MockSpan>>();
MockSpan* raw_span = mock_span.get();
EXPECT_CALL(*raw_span, End());
{
ScopedSpan scoped_span(std::move(mock_span));
EXPECT_EQ(scoped_span.get(), raw_span);
EXPECT_EQ(scoped_span.operator->(), raw_span);
}
}
TEST(ScopedSpanTest, ForwardsSetStatus) {
auto mock_span = std::make_unique<StrictMock<MockSpan>>();
MockSpan* raw_span = mock_span.get();
absl::Status test_status = absl::InternalError("test error");
EXPECT_CALL(*raw_span, SetStatus(test_status));
EXPECT_CALL(*raw_span, End());
ScopedSpan scoped_span(std::move(mock_span));
scoped_span.SetStatus(test_status);
}
TEST(ScopedSpanTest, MoveConstructorTransfersOwnership) {
auto mock_span = std::make_unique<StrictMock<MockSpan>>();
MockSpan* raw_span = mock_span.get();
EXPECT_CALL(*raw_span, End());
{
ScopedSpan scoped1(std::move(mock_span));
EXPECT_EQ(ThreadContext::GetCurrentSpan(), raw_span);
ScopedSpan scoped2(std::move(scoped1));
EXPECT_EQ(scoped1.get(), nullptr); // NOLINT(bugprone-use-after-move)
EXPECT_EQ(scoped2.get(), raw_span);
EXPECT_EQ(ThreadContext::GetCurrentSpan(), raw_span);
}
EXPECT_EQ(ThreadContext::GetCurrentSpan(), nullptr);
}
TEST(ScopedSpanTest, MoveAssignmentTransfersOwnership) {
auto mock_span1 = std::make_unique<StrictMock<MockSpan>>();
MockSpan* raw_span1 = mock_span1.get();
auto mock_span2 = std::make_unique<StrictMock<MockSpan>>();
MockSpan* raw_span2 = mock_span2.get();
EXPECT_CALL(*raw_span1, End());
EXPECT_CALL(*raw_span2, End());
{
ScopedSpan scoped1(std::move(mock_span1));
ScopedSpan scoped2(std::move(mock_span2));
EXPECT_EQ(ThreadContext::GetCurrentSpan(), raw_span2);
scoped2 = std::move(scoped1);
EXPECT_EQ(scoped1.get(), nullptr); // NOLINT(bugprone-use-after-move)
EXPECT_EQ(scoped2.get(), raw_span1);
EXPECT_EQ(ThreadContext::GetCurrentSpan(), raw_span1);
}
EXPECT_EQ(ThreadContext::GetCurrentSpan(), nullptr);
}
TEST(ScopedSpanTest, MoveAssignmentOrderAndSequence) {
auto mock_span1 = std::make_unique<StrictMock<MockSpan>>();
MockSpan* raw_span1 = mock_span1.get();
auto mock_span2 = std::make_unique<StrictMock<MockSpan>>();
MockSpan* raw_span2 = mock_span2.get();
InSequence seq;
EXPECT_CALL(*raw_span2, End());
EXPECT_CALL(*raw_span1, End());
{
ScopedSpan scoped1(std::move(mock_span1));
ScopedSpan scoped2(std::move(mock_span2));
scoped2 = std::move(scoped1);
}
}
TEST(ScopedSpanTest, MoveAssignmentToNullSpan) {
auto mock_span = std::make_unique<StrictMock<MockSpan>>();
MockSpan* raw_span = mock_span.get();
EXPECT_CALL(*raw_span, End());
{
ScopedSpan scoped1(std::move(mock_span));
ScopedSpan scoped2(nullptr);
scoped2 = std::move(scoped1);
EXPECT_EQ(scoped1.get(), nullptr); // NOLINT(bugprone-use-after-move)
EXPECT_EQ(scoped2.get(), raw_span);
}
}
TEST(ScopedSpanTest, SelfMoveAssignment) {
auto mock_span = std::make_unique<StrictMock<MockSpan>>();
MockSpan* raw_span = mock_span.get();
EXPECT_CALL(*raw_span, End()).Times(1);
{
ScopedSpan scoped(std::move(mock_span));
ScopedSpan& scoped_ref = scoped;
scoped = std::move(scoped_ref);
EXPECT_EQ(scoped.get(), raw_span);
}
}
TEST(ScopedSpanTest, NullPointerSafe) {
ScopedSpan null_scoped(nullptr);
EXPECT_EQ(null_scoped.get(), nullptr);
// SetStatus on null span should not crash
null_scoped.SetStatus(absl::OkStatus());
// Destruction of null span should not crash
}
TEST(ScopedSpanTest, NestedSpanHierarchyRestoresParent) {
auto mock_outer = std::make_unique<StrictMock<MockSpan>>();
MockSpan* raw_outer = mock_outer.get();
auto mock_inner = std::make_unique<StrictMock<MockSpan>>();
MockSpan* raw_inner = mock_inner.get();
EXPECT_CALL(*raw_inner, End());
EXPECT_CALL(*raw_outer, End());
EXPECT_EQ(ThreadContext::GetCurrentSpan(), nullptr);
{
ScopedSpan outer(std::move(mock_outer));
EXPECT_EQ(ThreadContext::GetCurrentSpan(), raw_outer);
{
ScopedSpan inner(std::move(mock_inner));
EXPECT_EQ(ThreadContext::GetCurrentSpan(), raw_inner);
}
EXPECT_EQ(ThreadContext::GetCurrentSpan(), raw_outer);
}
EXPECT_EQ(ThreadContext::GetCurrentSpan(), nullptr);
}
TEST(ScopedSpanTest, MovedFromScopedSpanDestructionDoesNotRestoreSpan) {
MockSpan outer_span;
ThreadContext::SetCurrentSpan(&outer_span);
auto mock_span = std::make_unique<StrictMock<MockSpan>>();
MockSpan* raw_span = mock_span.get();
EXPECT_CALL(*raw_span, End());
{
std::optional<ScopedSpan> scoped2;
{
ScopedSpan scoped1(std::move(mock_span));
EXPECT_EQ(ThreadContext::GetCurrentSpan(), raw_span);
scoped2.emplace(std::move(scoped1));
EXPECT_EQ(ThreadContext::GetCurrentSpan(), raw_span);
// scoped1 is destroyed here at end of inner scope.
}
// Destruction of moved-from scoped1 must not unbind raw_span or restore
// outer_span.
EXPECT_EQ(ThreadContext::GetCurrentSpan(), raw_span);
}
EXPECT_EQ(ThreadContext::GetCurrentSpan(), &outer_span);
ThreadContext::SetCurrentSpan(nullptr);
}
TEST(ScopedSpanTest, MoveAssignmentIntoInactiveScopedSpanActivatesTarget) {
auto mock_span = std::make_unique<StrictMock<MockSpan>>();
MockSpan* raw_span = mock_span.get();
EXPECT_CALL(*raw_span, End());
ScopedSpan target(nullptr);
{
ScopedSpan sink(std::move(target));
}
// target is now inactive.
EXPECT_EQ(target.get(), nullptr); // NOLINT(bugprone-use-after-move)
EXPECT_EQ(ThreadContext::GetCurrentSpan(), nullptr);
{
ScopedSpan source(std::move(mock_span));
EXPECT_EQ(ThreadContext::GetCurrentSpan(), raw_span);
// Assigning active source into inactive target must activate target.
target = std::move(source);
EXPECT_EQ(target.get(), raw_span);
EXPECT_EQ(ThreadContext::GetCurrentSpan(), raw_span);
}
// source went out of scope (inactive).
// target remains active in outer scope with raw_span.
EXPECT_EQ(ThreadContext::GetCurrentSpan(), raw_span);
}
TEST(ScopedSpanTest, MoveAssignmentFromInactiveScopedSpanDeactivatesTarget) {
MockSpan outer_span;
ThreadContext::SetCurrentSpan(&outer_span);
auto mock_span = std::make_unique<StrictMock<MockSpan>>();
MockSpan* raw_span = mock_span.get();
EXPECT_CALL(*raw_span, End());
{
ScopedSpan inactive_scoped(nullptr);
{
ScopedSpan sink(std::move(inactive_scoped));
}
// inactive_scoped is now inactive.
EXPECT_EQ(ThreadContext::GetCurrentSpan(), &outer_span);
ScopedSpan active_scoped(std::move(mock_span));
EXPECT_EQ(ThreadContext::GetCurrentSpan(), raw_span);
// Assigning inactive into active must end active_scoped's span,
// restore outer_span, and deactivate active_scoped.
active_scoped =
std::move(inactive_scoped); // NOLINT(bugprone-use-after-move)
EXPECT_EQ(active_scoped.get(), nullptr);
EXPECT_EQ(ThreadContext::GetCurrentSpan(), &outer_span);
}
// Destruction of deactivated active_scoped must not change ThreadContext.
EXPECT_EQ(ThreadContext::GetCurrentSpan(), &outer_span);
ThreadContext::SetCurrentSpan(nullptr);
}
TEST(ScopedSpanTest, RawSpanConstructorBindsAndUnbindsWithoutCallingEnd) {
StrictMock<MockSpan> raw_span;
// No End() call expected for non-owning ScopedSpan(Span*).
EXPECT_EQ(ThreadContext::GetCurrentSpan(), nullptr);
{
ScopedSpan scoped(&raw_span);
EXPECT_EQ(scoped.get(), &raw_span);
EXPECT_EQ(ThreadContext::GetCurrentSpan(), &raw_span);
}
EXPECT_EQ(ThreadContext::GetCurrentSpan(), nullptr);
}
TEST(ScopedSpanTest, RawSpanConstructorWithNullAndActiveContext) {
StrictMock<MockSpan> outer_span;
ThreadContext::SetCurrentSpan(&outer_span);
{
ScopedSpan null_scoped(static_cast<Span*>(nullptr));
EXPECT_EQ(null_scoped.get(), nullptr);
EXPECT_EQ(ThreadContext::GetCurrentSpan(), &outer_span);
}
EXPECT_EQ(ThreadContext::GetCurrentSpan(), &outer_span);
ThreadContext::SetCurrentSpan(nullptr);
}
TEST(ScopedSpanTest, UniquePtrConstructorWithNullAndActiveContext) {
StrictMock<MockSpan> outer_span;
ThreadContext::SetCurrentSpan(&outer_span);
{
ScopedSpan null_scoped(std::unique_ptr<Span>(nullptr));
EXPECT_EQ(null_scoped.get(), nullptr);
EXPECT_EQ(ThreadContext::GetCurrentSpan(), &outer_span);
}
EXPECT_EQ(ThreadContext::GetCurrentSpan(), &outer_span);
ThreadContext::SetCurrentSpan(nullptr);
}
TEST(ScopedSpanTest, MoveConstructorWithRawSpan) {
StrictMock<MockSpan> raw_span;
{
ScopedSpan scoped1(&raw_span);
EXPECT_EQ(ThreadContext::GetCurrentSpan(), &raw_span);
ScopedSpan scoped2(std::move(scoped1));
EXPECT_EQ(scoped1.get(), nullptr); // NOLINT(bugprone-use-after-move)
EXPECT_EQ(scoped2.get(), &raw_span);
EXPECT_EQ(ThreadContext::GetCurrentSpan(), &raw_span);
}
EXPECT_EQ(ThreadContext::GetCurrentSpan(), nullptr);
}
TEST(ScopedSpanTest, MoveAssignmentWithRawSpan) {
StrictMock<MockSpan> raw_span1;
StrictMock<MockSpan> raw_span2;
{
ScopedSpan scoped1(&raw_span1);
ScopedSpan scoped2(&raw_span2);
EXPECT_EQ(ThreadContext::GetCurrentSpan(), &raw_span2);
scoped2 = std::move(scoped1);
EXPECT_EQ(scoped1.get(), nullptr); // NOLINT(bugprone-use-after-move)
EXPECT_EQ(scoped2.get(), &raw_span1);
EXPECT_EQ(ThreadContext::GetCurrentSpan(), &raw_span1);
}
EXPECT_EQ(ThreadContext::GetCurrentSpan(), nullptr);
}
} // namespace
} // namespace credentio_tracing