blob: 423489626396e5f699c458c4572342e167873f56 [file]
// 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