diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 09:44:33 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 09:44:33 +0000 |
commit | b196c6498d22e47bb9d0da0153068ec54eac7956 (patch) | |
tree | 1a994a492581e93224a7ee6455f5d4e9d2ec8e59 /mocktracer/test | |
parent | Initial commit. (diff) | |
download | opentracing-cpp-b196c6498d22e47bb9d0da0153068ec54eac7956.tar.xz opentracing-cpp-b196c6498d22e47bb9d0da0153068ec54eac7956.zip |
Adding upstream version 1.6.0.upstream/1.6.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'mocktracer/test')
-rw-r--r-- | mocktracer/test/BUILD | 15 | ||||
-rw-r--r-- | mocktracer/test/CMakeLists.txt | 21 | ||||
-rw-r--r-- | mocktracer/test/json_test.cpp | 76 | ||||
-rw-r--r-- | mocktracer/test/propagation_test.cpp | 236 | ||||
-rw-r--r-- | mocktracer/test/tracer_factory_test.cpp | 68 | ||||
-rw-r--r-- | mocktracer/test/tracer_test.cpp | 190 |
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()); + } +} |