summaryrefslogtreecommitdiffstats
path: root/mocktracer/test
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--mocktracer/test/BUILD15
-rw-r--r--mocktracer/test/CMakeLists.txt21
-rw-r--r--mocktracer/test/json_test.cpp76
-rw-r--r--mocktracer/test/propagation_test.cpp236
-rw-r--r--mocktracer/test/tracer_factory_test.cpp68
-rw-r--r--mocktracer/test/tracer_test.cpp190
6 files changed, 606 insertions, 0 deletions
diff --git a/mocktracer/test/BUILD b/mocktracer/test/BUILD
new file mode 100644
index 0000000..e26b6bb
--- /dev/null
+++ b/mocktracer/test/BUILD
@@ -0,0 +1,15 @@
+TEST_NAMES = [
+ "propagation_test",
+ "tracer_test",
+ "tracer_factory_test",
+ "json_test",
+]
+
+[cc_test(
+ name = test_name,
+ srcs = [test_name + ".cpp"],
+ deps = [
+ "//mocktracer:mocktracer",
+ "//3rd_party:catch2",
+ ],
+) for test_name in TEST_NAMES]
diff --git a/mocktracer/test/CMakeLists.txt b/mocktracer/test/CMakeLists.txt
new file mode 100644
index 0000000..f12033e
--- /dev/null
+++ b/mocktracer/test/CMakeLists.txt
@@ -0,0 +1,21 @@
+if (BUILD_SHARED_LIBS)
+ set(OPENTRACING_MOCKTRACER_LIBRARY opentracing_mocktracer)
+else()
+ set(OPENTRACING_MOCKTRACER_LIBRARY opentracing_mocktracer-static)
+endif()
+
+add_executable(mocktracer_tracer_test tracer_test.cpp)
+target_link_libraries(mocktracer_tracer_test ${OPENTRACING_MOCKTRACER_LIBRARY})
+add_test(NAME mocktracer_tracer_test COMMAND mocktracer_tracer_test)
+
+add_executable(mocktracer_tracer_factory_test tracer_factory_test.cpp)
+target_link_libraries(mocktracer_tracer_factory_test ${OPENTRACING_MOCKTRACER_LIBRARY})
+add_test(NAME mocktracer_tracer_factory_test COMMAND mocktracer_tracer_factory_test)
+
+add_executable(mocktracer_propagation_test propagation_test.cpp)
+target_link_libraries(mocktracer_propagation_test ${OPENTRACING_MOCKTRACER_LIBRARY})
+add_test(NAME mocktracer_propagation_test COMMAND mocktracer_propagation_test)
+
+add_executable(mocktracer_json_test json_test.cpp)
+target_link_libraries(mocktracer_json_test ${OPENTRACING_MOCKTRACER_LIBRARY})
+add_test(NAME mocktracer_json_test COMMAND mocktracer_json_test)
diff --git a/mocktracer/test/json_test.cpp b/mocktracer/test/json_test.cpp
new file mode 100644
index 0000000..d53d29b
--- /dev/null
+++ b/mocktracer/test/json_test.cpp
@@ -0,0 +1,76 @@
+#include <opentracing/mocktracer/in_memory_recorder.h>
+#include <opentracing/mocktracer/json.h>
+#include <opentracing/mocktracer/tracer.h>
+#include <algorithm>
+#include <cctype>
+
+#define CATCH_CONFIG_MAIN
+#include <opentracing/catch2/catch.hpp>
+using namespace opentracing;
+using namespace mocktracer;
+
+TEST_CASE("json") {
+ auto recorder = new InMemoryRecorder{};
+ MockTracerOptions tracer_options;
+ tracer_options.recorder.reset(recorder);
+ auto tracer = std::shared_ptr<opentracing::Tracer>{
+ new MockTracer{std::move(tracer_options)}};
+
+ SpanContextData span_context_data;
+ span_context_data.trace_id = 123;
+ span_context_data.span_id = 456;
+ span_context_data.baggage = {{"b1", "v1"}, {"b2", "v2"}};
+
+ SpanData span_data;
+ span_data.span_context = span_context_data;
+ span_data.references = {{SpanReferenceType::ChildOfRef, 123, 457}};
+ span_data.operation_name = "o1";
+ span_data.start_timestamp =
+ std::chrono::system_clock::time_point{} + std::chrono::hours{51};
+ span_data.duration = std::chrono::microseconds{92};
+ span_data.tags = {{"t1", 123}, {"t2", "cat"}};
+ span_data.logs = {{span_data.start_timestamp, {{"l1", 1}, {"l2", 1.5}}}};
+ std::ostringstream oss;
+ ToJson(oss, {span_data});
+
+ std::string expected_serialization = R"(
+ [{
+ "span_context": {
+ "trace_id": "000000000000007b",
+ "span_id": "00000000000001c8",
+ "baggage": {
+ "b1": "v1",
+ "b2": "v2"
+ }
+ },
+ "references": [{
+ "reference_type": "CHILD_OF",
+ "trace_id": "000000000000007b",
+ "span_id": "00000000000001c9"
+ }],
+ "operation_name": "o1",
+ "start_timestamp": 183600000000,
+ "duration": 92,
+ "tags": {
+ "t1": 123,
+ "t2": "cat"
+ },
+ "logs": [{
+ "timestamp": 183600000000,
+ "fields": [{
+ "key": "l1",
+ "value": 1
+ }, {
+ "key": "l2",
+ "value": 1.5
+ }]
+ }]
+ }])";
+ expected_serialization.erase(
+ std::remove_if(expected_serialization.begin(),
+ expected_serialization.end(),
+ [](char c) { return std::isspace(c); }),
+ expected_serialization.end());
+
+ CHECK(oss.str() == expected_serialization);
+}
diff --git a/mocktracer/test/propagation_test.cpp b/mocktracer/test/propagation_test.cpp
new file mode 100644
index 0000000..00b04ee
--- /dev/null
+++ b/mocktracer/test/propagation_test.cpp
@@ -0,0 +1,236 @@
+#include <opentracing/mocktracer/in_memory_recorder.h>
+#include <opentracing/mocktracer/tracer.h>
+#include <opentracing/noop.h>
+#include <sstream>
+#include <string>
+#include <unordered_map>
+
+#define CATCH_CONFIG_MAIN
+#include <opentracing/catch2/catch.hpp>
+using namespace opentracing;
+using namespace mocktracer;
+
+struct TextMapCarrier : TextMapReader, TextMapWriter {
+ TextMapCarrier(std::unordered_map<std::string, std::string>& text_map_)
+ : text_map(text_map_) {}
+
+ expected<void> Set(string_view key, string_view value) const override {
+ text_map[key] = value;
+ return {};
+ }
+
+ expected<string_view> LookupKey(string_view key) const override {
+ if (!supports_lookup) {
+ return make_unexpected(lookup_key_not_supported_error);
+ }
+ auto iter = text_map.find(key);
+ if (iter != text_map.end()) {
+ return string_view{iter->second};
+ } else {
+ return make_unexpected(key_not_found_error);
+ }
+ }
+
+ expected<void> ForeachKey(
+ std::function<expected<void>(string_view key, string_view value)> f)
+ const override {
+ ++foreach_key_call_count;
+ for (const auto& key_value : text_map) {
+ auto result = f(key_value.first, key_value.second);
+ if (!result) return result;
+ }
+ return {};
+ }
+
+ bool supports_lookup = false;
+ mutable int foreach_key_call_count = 0;
+ std::unordered_map<std::string, std::string>& text_map;
+};
+
+struct HTTPHeadersCarrier : HTTPHeadersReader, HTTPHeadersWriter {
+ HTTPHeadersCarrier(std::unordered_map<std::string, std::string>& text_map_)
+ : text_map(text_map_) {}
+
+ expected<void> Set(string_view key, string_view value) const override {
+ text_map[key] = value;
+ return {};
+ }
+
+ expected<void> ForeachKey(
+ std::function<expected<void>(string_view key, string_view value)> f)
+ const override {
+ for (const auto& key_value : text_map) {
+ auto result = f(key_value.first, key_value.second);
+ if (!result) return result;
+ }
+ return {};
+ }
+
+ std::unordered_map<std::string, std::string>& text_map;
+};
+
+TEST_CASE("propagation") {
+ const char* propagation_key = "propagation-key";
+ auto recorder = new InMemoryRecorder{};
+ MockTracerOptions tracer_options;
+ tracer_options.propagation_options.propagation_key = propagation_key;
+ tracer_options.recorder.reset(recorder);
+ auto tracer = std::shared_ptr<opentracing::Tracer>{
+ new MockTracer{std::move(tracer_options)}};
+ std::unordered_map<std::string, std::string> text_map;
+ TextMapCarrier text_map_carrier(text_map);
+ HTTPHeadersCarrier http_headers_carrier(text_map);
+ auto span = tracer->StartSpan("a");
+ CHECK(span);
+ span->SetBaggageItem("abc", "123");
+
+ SECTION("Propagation uses the specified propagation_key.") {
+ CHECK(tracer->Inject(span->context(), text_map_carrier));
+ CHECK(text_map.count(propagation_key) == 1);
+ }
+
+ SECTION("Inject, extract, inject yields the same text_map.") {
+ CHECK(tracer->Inject(span->context(), text_map_carrier));
+ auto injection_map1 = text_map;
+ auto span_context_maybe = tracer->Extract(text_map_carrier);
+ CHECK((span_context_maybe && span_context_maybe->get()));
+ text_map.clear();
+ CHECK(tracer->Inject(*span_context_maybe->get(), text_map_carrier));
+ CHECK(injection_map1 == text_map);
+ }
+
+ SECTION("Inject, extract, inject yields the same binary blob.") {
+ std::ostringstream oss(std::ios::binary);
+ CHECK(tracer->Inject(span->context(), oss));
+ auto blob = oss.str();
+ std::istringstream iss(blob, std::ios::binary);
+ auto span_context_maybe = tracer->Extract(iss);
+ CHECK((span_context_maybe && span_context_maybe->get()));
+ std::ostringstream oss2(std::ios::binary);
+ CHECK(tracer->Inject(*span_context_maybe->get(), oss2));
+ CHECK(blob == oss2.str());
+ }
+
+ SECTION(
+ "Extracing a context from an empty text-map gives a null span context.") {
+ auto span_context_maybe = tracer->Extract(text_map_carrier);
+ CHECK(span_context_maybe);
+ CHECK(span_context_maybe->get() == nullptr);
+ }
+
+ SECTION("Injecting a non-Mock span returns invalid_span_context_error.") {
+ auto noop_tracer = opentracing::MakeNoopTracer();
+ CHECK(noop_tracer);
+ auto noop_span = noop_tracer->StartSpan("a");
+ CHECK(noop_span);
+ auto was_successful =
+ tracer->Inject(noop_span->context(), text_map_carrier);
+ CHECK(!was_successful);
+ CHECK(was_successful.error() == opentracing::invalid_span_context_error);
+ }
+
+ SECTION("Extract is insensitive to changes in case for http header fields") {
+ CHECK(tracer->Inject(span->context(), http_headers_carrier));
+
+ // Change the case of one of the fields.
+ auto key_value = *std::begin(text_map);
+ text_map.erase(std::begin(text_map));
+ auto key = key_value.first;
+ key[0] = key[0] == std::toupper(key[0])
+ ? static_cast<char>(std::tolower(key[0]))
+ : static_cast<char>(std::toupper(key[0]));
+ text_map[key] = key_value.second;
+ CHECK(tracer->Extract(http_headers_carrier));
+ }
+
+ SECTION("Extract/Inject fail if a stream has failure bits set.") {
+ std::ostringstream oss(std::ios::binary);
+ oss.setstate(std::ios_base::failbit);
+ CHECK(!tracer->Inject(span->context(), oss));
+ oss.clear();
+ CHECK(tracer->Inject(span->context(), oss));
+ auto blob = oss.str();
+ std::istringstream iss(blob, std::ios::binary);
+ iss.setstate(std::ios_base::failbit);
+ CHECK(!tracer->Extract(iss));
+ }
+
+ SECTION(
+ "Extracting a span from an invalid binary blob returns "
+ "an error.") {
+ std::string invalid_context = "abc123xyz321qrs42";
+ std::istringstream iss{invalid_context, std::ios::binary};
+ auto span_context_maybe = tracer->Extract(iss);
+ CHECK(!span_context_maybe);
+ }
+
+ SECTION("Calling Extract on an empty stream yields a nullptr.") {
+ std::string blob;
+ std::istringstream iss(blob, std::ios::binary);
+ auto span_context_maybe = tracer->Extract(iss);
+ CHECK(span_context_maybe);
+ CHECK(span_context_maybe->get() == nullptr);
+ }
+
+ SECTION("If a carrier supports LookupKey, then ForeachKey won't be called") {
+ CHECK(tracer->Inject(span->context(), text_map_carrier));
+ CHECK(text_map.size() == 1);
+ text_map_carrier.supports_lookup = true;
+ auto span_context_maybe = tracer->Extract(text_map_carrier);
+ CHECK((span_context_maybe && span_context_maybe->get()));
+ CHECK(text_map_carrier.foreach_key_call_count == 0);
+ }
+
+ SECTION(
+ "When LookupKey is used, a nullptr is returned if there is no "
+ "span_context") {
+ text_map.clear();
+ text_map_carrier.supports_lookup = true;
+ auto span_context_maybe = tracer->Extract(text_map_carrier);
+ CHECK((span_context_maybe && span_context_maybe->get() == nullptr));
+ CHECK(text_map_carrier.foreach_key_call_count == 0);
+ }
+
+ SECTION("Verify only valid base64 characters are used.") {
+ CHECK(tracer->Inject(span->context(), text_map_carrier));
+ CHECK(text_map.size() == 1);
+ // Follows the guidelines given in RFC-4648 on what characters are
+ // permissible. See
+ // http://www.rfc-editor.org/rfc/rfc4648.txt
+ auto iter = text_map.begin();
+ CHECK(iter != text_map.end());
+ auto value = iter->second;
+ auto is_base64_char = [](char c) {
+ return ('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z') ||
+ ('0' <= c && c <= '9') || c == '+' || c == '/' || c == '=';
+ };
+ CHECK(std::all_of(value.begin(), value.end(), is_base64_char));
+ CHECK(value.size() % 4 == 0);
+ }
+
+ SECTION("Inject fails if inject_error_code is non-zero.") {
+ MockTracerOptions tracer_options_fail;
+ auto error_code = std::make_error_code(std::errc::network_down);
+ tracer_options_fail.propagation_options.inject_error_code = error_code;
+ tracer = std::shared_ptr<opentracing::Tracer>{
+ new MockTracer{std::move(tracer_options_fail)}};
+
+ std::ostringstream oss;
+ auto rcode = tracer->Inject(span->context(), oss);
+ CHECK(!rcode);
+ CHECK(rcode.error() == error_code);
+ }
+
+ SECTION("Extract fails if extract_error_code is non-zero.") {
+ MockTracerOptions tracer_options_fail;
+ auto error_code = std::make_error_code(std::errc::network_down);
+ tracer_options_fail.propagation_options.extract_error_code = error_code;
+ tracer = std::shared_ptr<opentracing::Tracer>{
+ new MockTracer{std::move(tracer_options_fail)}};
+
+ CHECK(tracer->Inject(span->context(), text_map_carrier));
+ auto span_context_maybe = tracer->Extract(text_map_carrier);
+ CHECK(!span_context_maybe);
+ CHECK(span_context_maybe.error() == error_code);
+ }
+}
diff --git a/mocktracer/test/tracer_factory_test.cpp b/mocktracer/test/tracer_factory_test.cpp
new file mode 100644
index 0000000..a27b87c
--- /dev/null
+++ b/mocktracer/test/tracer_factory_test.cpp
@@ -0,0 +1,68 @@
+#include <opentracing/mocktracer/tracer_factory.h>
+#include <cstdio>
+#include <random>
+#include <string>
+
+#define CATCH_CONFIG_MAIN
+#include <opentracing/catch2/catch.hpp>
+using namespace opentracing;
+using namespace mocktracer;
+
+TEST_CASE("tracer_factory") {
+ MockTracerFactory tracer_factory;
+ std::string error_message;
+
+ SECTION("Creating a tracer from a nullptr yields an error.") {
+ const char* configuration = nullptr;
+ auto tracer_maybe = tracer_factory.MakeTracer(configuration, error_message);
+ REQUIRE(!tracer_maybe);
+ REQUIRE(!error_message.empty());
+ }
+
+ SECTION("Creating a tracer from an empty string yields an error.") {
+ const char* configuration = "";
+ auto tracer_maybe = tracer_factory.MakeTracer(configuration, error_message);
+ REQUIRE(!tracer_maybe);
+ REQUIRE(!error_message.empty());
+ }
+
+ SECTION("Creating a tracer from invalid JSON yields an error.") {
+ const char* configuration = "{ abc";
+ auto tracer_maybe = tracer_factory.MakeTracer(configuration, error_message);
+ REQUIRE(!tracer_maybe);
+ REQUIRE(!error_message.empty());
+ }
+
+ SECTION(
+ "Creating a tracer from valid JSON but an invalid configuration "
+ "yields an error.") {
+ const char* configuration = R"({ "abc": 123 })";
+ auto tracer_maybe = tracer_factory.MakeTracer(configuration, error_message);
+ REQUIRE(!tracer_maybe);
+ REQUIRE(!error_message.empty());
+ REQUIRE(tracer_maybe.error() == invalid_configuration_error);
+ }
+
+ SECTION("Creating a tracer with an invalid output_file yields an error.") {
+ const char* configuration = R"({ "output_file": "" })";
+ auto tracer_maybe = tracer_factory.MakeTracer(configuration, error_message);
+ REQUIRE(!tracer_maybe);
+ REQUIRE(!error_message.empty());
+ REQUIRE(tracer_maybe.error() == invalid_configuration_error);
+ }
+
+ SECTION("Creating a tracer with a valid config succeeds.") {
+ std::string span_filename{"spans."};
+ const auto random_id = std::random_device{}();
+ span_filename.append(std::to_string(random_id));
+ std::string configuration = R"({ "output_file": ")";
+ configuration.append(span_filename);
+ configuration.append(R"(" })");
+
+ auto tracer_maybe =
+ tracer_factory.MakeTracer(configuration.c_str(), error_message);
+ REQUIRE(tracer_maybe);
+
+ std::remove(span_filename.c_str());
+ }
+}
diff --git a/mocktracer/test/tracer_test.cpp b/mocktracer/test/tracer_test.cpp
new file mode 100644
index 0000000..cbb1a6f
--- /dev/null
+++ b/mocktracer/test/tracer_test.cpp
@@ -0,0 +1,190 @@
+#include <opentracing/mocktracer/in_memory_recorder.h>
+#include <opentracing/mocktracer/json.h>
+#include <opentracing/mocktracer/json_recorder.h>
+#include <opentracing/mocktracer/tracer.h>
+#include <opentracing/noop.h>
+#include <sstream>
+
+#define CATCH_CONFIG_MAIN
+#include <opentracing/catch2/catch.hpp>
+using namespace opentracing;
+using namespace mocktracer;
+
+TEST_CASE("tracer") {
+ auto recorder = new InMemoryRecorder{};
+ MockTracerOptions tracer_options;
+ tracer_options.recorder.reset(recorder);
+ auto tracer = std::shared_ptr<opentracing::Tracer>{
+ new MockTracer{std::move(tracer_options)}};
+
+ SECTION("MockTracer can be constructed without a recorder.") {
+ auto norecorder_tracer = std::shared_ptr<opentracing::Tracer>{
+ new MockTracer{MockTracerOptions{}}};
+ auto span = norecorder_tracer->StartSpan("a");
+ }
+
+ SECTION("StartSpan applies the provided tags.") {
+ {
+ auto span =
+ tracer->StartSpan("a", {SetTag("abc", 123), SetTag("xyz", true)});
+ CHECK(span);
+ span->Finish();
+ }
+ auto span = recorder->top();
+ CHECK(span.operation_name == "a");
+ std::map<std::string, Value> expected_tags = {{"abc", 123}, {"xyz", true}};
+ CHECK(span.tags == expected_tags);
+ }
+
+ SECTION("You can set a single child-of reference when starting a span.") {
+ auto span_a = tracer->StartSpan("a");
+ CHECK(span_a);
+ span_a->Finish();
+ auto span_b = tracer->StartSpan("b", {ChildOf(&span_a->context())});
+ CHECK(span_b);
+ span_b->Finish();
+ auto spans = recorder->spans();
+ CHECK(spans.at(0).span_context.trace_id ==
+ spans.at(1).span_context.trace_id);
+ std::vector<SpanReferenceData> expected_references = {
+ {SpanReferenceType::ChildOfRef, spans.at(0).span_context.trace_id,
+ spans.at(0).span_context.span_id}};
+ CHECK(spans.at(1).references == expected_references);
+ }
+
+ SECTION("You can set a single follows-from reference when starting a span.") {
+ auto span_a = tracer->StartSpan("a");
+ CHECK(span_a);
+ span_a->Finish();
+ auto span_b = tracer->StartSpan("b", {FollowsFrom(&span_a->context())});
+ CHECK(span_b);
+ span_b->Finish();
+ auto spans = recorder->spans();
+ CHECK(spans.at(0).span_context.trace_id ==
+ spans.at(1).span_context.trace_id);
+ std::vector<SpanReferenceData> expected_references = {
+ {SpanReferenceType::FollowsFromRef, spans.at(0).span_context.trace_id,
+ spans.at(0).span_context.span_id}};
+ CHECK(spans.at(1).references == expected_references);
+ }
+
+ SECTION("Multiple references are supported when starting a span.") {
+ auto span_a = tracer->StartSpan("a");
+ CHECK(span_a);
+ auto span_b = tracer->StartSpan("b");
+ CHECK(span_b);
+ auto span_c = tracer->StartSpan(
+ "c", {ChildOf(&span_a->context()), FollowsFrom(&span_b->context())});
+ span_a->Finish();
+ span_b->Finish();
+ span_c->Finish();
+ auto spans = recorder->spans();
+ std::vector<SpanReferenceData> expected_references = {
+ {SpanReferenceType::ChildOfRef, spans.at(0).span_context.trace_id,
+ spans.at(0).span_context.span_id},
+ {SpanReferenceType::FollowsFromRef, spans.at(1).span_context.trace_id,
+ spans.at(1).span_context.span_id}};
+ CHECK(spans.at(2).references == expected_references);
+ }
+
+ SECTION(
+ "Baggage from the span references are copied over to a new span "
+ "context") {
+ auto span_a = tracer->StartSpan("a");
+ CHECK(span_a);
+ span_a->SetBaggageItem("a", "1");
+ auto span_b = tracer->StartSpan("b");
+ CHECK(span_b);
+ span_b->SetBaggageItem("b", "2");
+ auto span_c = tracer->StartSpan(
+ "c", {ChildOf(&span_a->context()), ChildOf(&span_b->context())});
+ CHECK(span_c);
+ CHECK(span_c->BaggageItem("a") == "1");
+ CHECK(span_c->BaggageItem("b") == "2");
+ }
+
+ SECTION("References to non-MockTracer spans and null pointers are ignored.") {
+ auto noop_tracer = MakeNoopTracer();
+ auto noop_span = noop_tracer->StartSpan("noop");
+ CHECK(noop_span);
+ StartSpanOptions options;
+ options.references.push_back(
+ std::make_pair(SpanReferenceType::ChildOfRef, &noop_span->context()));
+ options.references.push_back(
+ std::make_pair(SpanReferenceType::ChildOfRef, nullptr));
+ auto span = tracer->StartSpanWithOptions("a", options);
+ CHECK(span);
+ span->Finish();
+ CHECK(recorder->top().references.size() == 0);
+ }
+
+ SECTION("Calling Finish a second time does nothing.") {
+ auto span = tracer->StartSpan("a");
+ CHECK(span);
+ span->Finish();
+ CHECK(recorder->size() == 1);
+ span->Finish();
+ CHECK(recorder->size() == 1);
+ }
+
+ SECTION("FinishWithOptions applies provided log records.") {
+ std::vector<LogRecord> logs;
+ {
+ auto span = tracer->StartSpan("a");
+ CHECK(span);
+ FinishSpanOptions options;
+ auto timestamp = SystemClock::now();
+ logs = {{timestamp, {{"abc", 123}}}};
+ options.log_records = logs;
+ span->FinishWithOptions(options);
+ }
+ auto span = recorder->top();
+ CHECK(span.operation_name == "a");
+ CHECK(span.logs == logs);
+ }
+
+ SECTION("Logs can be added to an active span.") {
+ {
+ auto span = tracer->StartSpan("a");
+ CHECK(span);
+ span->Log({{"abc", 123}});
+ }
+ auto span = recorder->top();
+ std::vector<std::pair<std::string, Value>> fields = {{"abc", 123}};
+ CHECK(span.logs.at(0).fields == fields);
+ }
+
+ SECTION("The operation name can be changed after the span is started.") {
+ auto span = tracer->StartSpan("a");
+ CHECK(span);
+ span->SetOperationName("b");
+ span->Finish();
+ CHECK(recorder->top().operation_name == "b");
+ }
+
+ SECTION("Tags can be specified after a span is started.") {
+ auto span = tracer->StartSpan("a");
+ CHECK(span);
+ span->SetTag("abc", 123);
+ span->Finish();
+ std::map<std::string, Value> expected_tags = {{"abc", 123}};
+ CHECK(recorder->top().tags == expected_tags);
+ }
+}
+
+TEST_CASE("json_recorder") {
+ auto oss = new std::ostringstream{};
+ MockTracerOptions tracer_options;
+ tracer_options.recorder = std::unique_ptr<Recorder>{
+ new JsonRecorder{std::unique_ptr<std::ostream>{oss}}};
+ auto tracer =
+ std::shared_ptr<Tracer>{new MockTracer{std::move(tracer_options)}};
+
+ SECTION("Spans are serialized to the stream upon Close.") {
+ auto span = tracer->StartSpan("a");
+ CHECK(span);
+ span->Finish();
+ tracer->Close();
+ CHECK(!oss->str().empty());
+ }
+}