summaryrefslogtreecommitdiffstats
path: root/mocktracer/src
diff options
context:
space:
mode:
Diffstat (limited to 'mocktracer/src')
-rw-r--r--mocktracer/src/base64.cpp174
-rw-r--r--mocktracer/src/base64.h48
-rw-r--r--mocktracer/src/dynamic_load.cpp39
-rw-r--r--mocktracer/src/in_memory_recorder.cpp33
-rw-r--r--mocktracer/src/json.cpp285
-rw-r--r--mocktracer/src/json_recorder.cpp30
-rw-r--r--mocktracer/src/mock_span.cpp206
-rw-r--r--mocktracer/src/mock_span.h66
-rw-r--r--mocktracer/src/mock_span_context.cpp40
-rw-r--r--mocktracer/src/mock_span_context.h79
-rw-r--r--mocktracer/src/propagation.cpp214
-rw-r--r--mocktracer/src/propagation.h39
-rw-r--r--mocktracer/src/tracer.cpp109
-rw-r--r--mocktracer/src/tracer_factory.cpp139
-rw-r--r--mocktracer/src/utility.cpp57
-rw-r--r--mocktracer/src/utility.h17
16 files changed, 1575 insertions, 0 deletions
diff --git a/mocktracer/src/base64.cpp b/mocktracer/src/base64.cpp
new file mode 100644
index 0000000..d039dcf
--- /dev/null
+++ b/mocktracer/src/base64.cpp
@@ -0,0 +1,174 @@
+/*
+ * Envoy
+ * Copyright 2016-2017 Lyft Inc.
+ *
+ * Licensed under Apache License 2.0. See LICENSE.apache for terms.
+ */
+
+#include "base64.h"
+
+#include <cstdint>
+#include <string>
+
+namespace opentracing {
+BEGIN_OPENTRACING_ABI_NAMESPACE
+namespace mocktracer {
+static constexpr char CHAR_TABLE[] =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+// Conversion table is taken from
+// https://opensource.apple.com/source/QuickTimeStreamingServer/QuickTimeStreamingServer-452/CommonUtilitiesLib/base64.c
+static const unsigned char REVERSE_LOOKUP_TABLE[256] = {
+ 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
+ 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
+ 64, 64, 64, 64, 64, 62, 64, 64, 64, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60,
+ 61, 64, 64, 64, 64, 64, 64, 64, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
+ 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 64, 64, 64, 64,
+ 64, 64, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42,
+ 43, 44, 45, 46, 47, 48, 49, 50, 51, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
+ 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
+ 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
+ 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
+ 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
+ 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
+ 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
+ 64, 64, 64, 64, 64, 64, 64, 64, 64};
+
+std::string Base64::decode(const char* input, size_t length) {
+ if (length % 4 || length == 0) {
+ return {};
+ }
+
+ // First position of "valid" padding character.
+ uint64_t first_padding_index = length;
+ int max_length = static_cast<int>(length) / 4 * 3;
+ // At most last two chars can be '='.
+ if (input[length - 1] == '=') {
+ max_length--;
+ first_padding_index = length - 1;
+ if (input[length - 2] == '=') {
+ max_length--;
+ first_padding_index = length - 2;
+ }
+ }
+ std::string result;
+ result.reserve(static_cast<size_t>(max_length));
+
+ uint64_t bytes_left = length;
+ uint64_t cur_read = 0;
+
+ // Read input string by group of 4 chars, length of input string must be
+ // divided evenly by 4. Decode 4 chars 6 bits each into 3 chars 8 bits each.
+ while (bytes_left > 0) {
+ // Take first 6 bits from 1st converted char and first 2 bits from 2nd
+ // converted char, make 8 bits char from it. Use conversion table to map
+ // char to decoded value (value is between 0 and 63 inclusive for a valid
+ // character).
+ const unsigned char a =
+ REVERSE_LOOKUP_TABLE[static_cast<uint32_t>(input[cur_read])];
+ const unsigned char b =
+ REVERSE_LOOKUP_TABLE[static_cast<uint32_t>(input[cur_read + 1])];
+ if (a == 64 || b == 64) {
+ // Input contains an invalid character.
+ return {};
+ }
+ result.push_back(static_cast<char>(a << 2 | b >> 4));
+ const unsigned char c =
+ REVERSE_LOOKUP_TABLE[static_cast<uint32_t>(input[cur_read + 2])];
+
+ // Decoded value 64 means invalid character unless we already know it is a
+ // valid padding. If so, following characters are all padding. Also we
+ // should check there are no unused bits.
+ if (c == 64) {
+ if (first_padding_index != cur_read + 2) {
+ // Input contains an invalid character.
+ return {};
+ } else if (b & 15) {
+ // There are unused bits at tail.
+ return {};
+ } else {
+ return result;
+ }
+ }
+ // Take last 4 bits from 2nd converted char and 4 first bits from 3rd
+ // converted char.
+ result.push_back(static_cast<char>(b << 4 | c >> 2));
+
+ const unsigned char d =
+ REVERSE_LOOKUP_TABLE[static_cast<uint32_t>(input[cur_read + 3])];
+ if (d == 64) {
+ if (first_padding_index != cur_read + 3) {
+ // Input contains an invalid character.
+ return {};
+ } else if (c & 3) {
+ // There are unused bits at tail.
+ return {};
+ } else {
+ return result;
+ }
+ }
+ // Take last 2 bits from 3rd converted char and all(6) bits from 4th
+ // converted char.
+ result.push_back(static_cast<char>(c << 6 | d));
+
+ cur_read += 4;
+ bytes_left -= 4;
+ }
+
+ return result;
+}
+
+void Base64::encodeBase(const uint8_t cur_char, uint64_t pos, uint8_t& next_c,
+ std::string& ret) {
+ switch (pos % 3) {
+ case 0:
+ ret.push_back(CHAR_TABLE[cur_char >> 2]);
+ next_c = static_cast<uint8_t>((cur_char & 0x03) << 4);
+ break;
+ case 1:
+ ret.push_back(CHAR_TABLE[next_c | (cur_char >> 4)]);
+ next_c = static_cast<uint8_t>((cur_char & 0x0f) << 2);
+ break;
+ case 2:
+ ret.push_back(CHAR_TABLE[next_c | (cur_char >> 6)]);
+ ret.push_back(CHAR_TABLE[cur_char & 0x3f]);
+ next_c = 0;
+ break;
+ }
+}
+
+void Base64::encodeLast(uint64_t pos, uint8_t last_char, std::string& ret) {
+ switch (pos % 3) {
+ case 1:
+ ret.push_back(CHAR_TABLE[last_char]);
+ ret.push_back('=');
+ ret.push_back('=');
+ break;
+ case 2:
+ ret.push_back(CHAR_TABLE[last_char]);
+ ret.push_back('=');
+ break;
+ default:
+ break;
+ }
+}
+
+std::string Base64::encode(const char* input, uint64_t length) {
+ uint64_t output_length = (length + 2) / 3 * 4;
+ std::string ret;
+ ret.reserve(output_length);
+
+ uint64_t pos = 0;
+ uint8_t next_c = 0;
+
+ for (uint64_t i = 0; i < length; ++i) {
+ encodeBase(static_cast<uint8_t>(input[i]), pos++, next_c, ret);
+ }
+
+ encodeLast(pos, next_c, ret);
+
+ return ret;
+}
+} // namespace mocktracer
+END_OPENTRACING_ABI_NAMESPACE
+} // namespace opentracing
diff --git a/mocktracer/src/base64.h b/mocktracer/src/base64.h
new file mode 100644
index 0000000..438bbda
--- /dev/null
+++ b/mocktracer/src/base64.h
@@ -0,0 +1,48 @@
+#ifndef OPENTRACING_MOCKTRACER_BASE64_H
+#define OPENTRACING_MOCKTRACER_BASE64_H
+
+#include <opentracing/version.h>
+#include <cstdint>
+#include <string>
+
+namespace opentracing {
+BEGIN_OPENTRACING_ABI_NAMESPACE
+namespace mocktracer {
+class Base64 {
+ public:
+ /**
+ * Base64 encode an input char buffer with a given length.
+ * @param input char array to encode.
+ * @param length of the input array.
+ */
+ static std::string encode(const char* input, uint64_t length);
+
+ /**
+ * Base64 decode an input string.
+ * @param input char array to decode.
+ * @param length of the input array.
+ *
+ * Note, decoded string may contain '\0' at any position, it should be treated
+ * as a sequence of bytes.
+ */
+ static std::string decode(const char* input, size_t length);
+
+ private:
+ /**
+ * Helper method for encoding. This is used to encode all of the characters
+ * from the input string.
+ */
+ static void encodeBase(const uint8_t cur_char, uint64_t pos, uint8_t& next_c,
+ std::string& ret);
+
+ /**
+ * Encode last characters. It appends '=' chars to the ret if input
+ * string length is not divisible by 3.
+ */
+ static void encodeLast(uint64_t pos, uint8_t last_char, std::string& ret);
+};
+} // namespace mocktracer
+END_OPENTRACING_ABI_NAMESPACE
+} // namespace opentracing
+
+#endif // OPENTRACING_MOCKTRACER_BASE64_H
diff --git a/mocktracer/src/dynamic_load.cpp b/mocktracer/src/dynamic_load.cpp
new file mode 100644
index 0000000..0fc7371
--- /dev/null
+++ b/mocktracer/src/dynamic_load.cpp
@@ -0,0 +1,39 @@
+#include <opentracing/dynamic_load.h>
+#include <opentracing/mocktracer/tracer_factory.h>
+#include <cstdio>
+#include <cstring>
+#include <exception>
+
+static int OpenTracingMakeTracerFactoryFct(const char* opentracing_version,
+ const char* opentracing_abi_version,
+ const void** error_category,
+ void* error_message,
+ void** tracer_factory) try {
+ if (opentracing_version == nullptr || opentracing_abi_version == nullptr ||
+ error_category == nullptr || tracer_factory == nullptr) {
+ fprintf(stderr,
+ "`opentracing_version`, `opentracing_abi_version`, "
+ "`error_category`, and `tracer_factory` must be non-null.\n");
+ std::terminate();
+ }
+
+ if (std::strcmp(opentracing_abi_version, OPENTRACING_ABI_VERSION) != 0) {
+ *error_category =
+ static_cast<const void*>(&opentracing::dynamic_load_error_category());
+ auto& message = *static_cast<std::string*>(error_message);
+ message =
+ "incompatible OpenTracing ABI versions; "
+ "expected " OPENTRACING_ABI_VERSION " but got ";
+ message.append(opentracing_abi_version);
+ return opentracing::incompatible_library_versions_error.value();
+ }
+
+ *tracer_factory = new opentracing::mocktracer::MockTracerFactory{};
+
+ return 0;
+} catch (const std::bad_alloc&) {
+ *error_category = static_cast<const void*>(&std::generic_category());
+ return static_cast<int>(std::errc::not_enough_memory);
+}
+
+OPENTRACING_DECLARE_IMPL_FACTORY(OpenTracingMakeTracerFactoryFct);
diff --git a/mocktracer/src/in_memory_recorder.cpp b/mocktracer/src/in_memory_recorder.cpp
new file mode 100644
index 0000000..818df3c
--- /dev/null
+++ b/mocktracer/src/in_memory_recorder.cpp
@@ -0,0 +1,33 @@
+#include <opentracing/mocktracer/in_memory_recorder.h>
+#include <stdexcept>
+
+namespace opentracing {
+BEGIN_OPENTRACING_ABI_NAMESPACE
+namespace mocktracer {
+void InMemoryRecorder::RecordSpan(SpanData&& span_data) noexcept try {
+ std::lock_guard<std::mutex> lock_guard{mutex_};
+ spans_.emplace_back(std::move(span_data));
+} catch (const std::exception&) {
+ // Drop span.
+}
+
+std::vector<SpanData> InMemoryRecorder::spans() const {
+ std::lock_guard<std::mutex> lock_guard{mutex_};
+ return spans_;
+}
+
+size_t InMemoryRecorder::size() const {
+ std::lock_guard<std::mutex> lock_guard{mutex_};
+ return spans_.size();
+}
+
+SpanData InMemoryRecorder::top() const {
+ std::lock_guard<std::mutex> lock_guard{mutex_};
+ if (spans_.empty()) {
+ throw std::runtime_error{"no spans"};
+ }
+ return spans_.back();
+}
+} // namespace mocktracer
+END_OPENTRACING_ABI_NAMESPACE
+} // namespace opentracing
diff --git a/mocktracer/src/json.cpp b/mocktracer/src/json.cpp
new file mode 100644
index 0000000..10c46d3
--- /dev/null
+++ b/mocktracer/src/json.cpp
@@ -0,0 +1,285 @@
+#include <opentracing/mocktracer/json.h>
+#include <opentracing/mocktracer/recorder.h>
+#include <opentracing/string_view.h>
+#include <cmath>
+#include <iomanip>
+#include <sstream>
+
+namespace opentracing {
+BEGIN_OPENTRACING_ABI_NAMESPACE
+namespace mocktracer {
+// The implementation is based off of this answer from StackOverflow:
+// https://stackoverflow.com/a/33799784
+static void WriteEscapedString(std::ostream& writer,
+ opentracing::string_view s) {
+ writer << '"';
+ for (char c : s) {
+ switch (c) {
+ case '"':
+ writer << R"(\")";
+ break;
+ case '\\':
+ writer << R"(\\)";
+ break;
+ case '\b':
+ writer << R"(\b)";
+ break;
+ case '\n':
+ writer << R"(\n)";
+ break;
+ case '\r':
+ writer << R"(\r)";
+ break;
+ case '\t':
+ writer << R"(\t)";
+ break;
+ default:
+ if ('\x00' <= c && c <= '\x1f') {
+ writer << R"(\u)";
+ writer << std::hex << std::setw(4) << std::setfill('0')
+ << static_cast<int>(c);
+ } else {
+ writer << c;
+ }
+ }
+ }
+ writer << '"';
+}
+
+static void WriteId(std::ostream& writer, uint64_t id) {
+ std::ostringstream oss;
+ oss << std::setfill('0') << std::setw(16) << std::hex << id;
+ if (!oss.good()) {
+ writer.setstate(std::ios::failbit);
+ return;
+ }
+ writer << '"' << oss.str() << '"';
+}
+
+static void ToJson(std::ostream& writer,
+ const SpanContextData& span_context_data) {
+ writer << '{';
+ writer << R"("trace_id":)";
+ WriteId(writer, span_context_data.trace_id);
+ writer << ',';
+
+ writer << R"("span_id":)";
+ WriteId(writer, span_context_data.span_id);
+ writer << ',';
+
+ writer << R"("baggage":{)";
+ auto num_baggage = span_context_data.baggage.size();
+ size_t baggage_index = 0;
+ for (auto& baggage_item : span_context_data.baggage) {
+ WriteEscapedString(writer, baggage_item.first);
+ writer << ':';
+ WriteEscapedString(writer, baggage_item.second);
+ if (++baggage_index < num_baggage) {
+ writer << ',';
+ }
+ }
+ writer << '}';
+ writer << '}';
+}
+
+static void ToJson(std::ostream& writer,
+ const SpanReferenceData& span_reference_data) {
+ writer << '{';
+ writer << R"("reference_type":)";
+ if (span_reference_data.reference_type == SpanReferenceType::ChildOfRef) {
+ writer << R"("CHILD_OF")";
+ } else {
+ writer << R"("FOLLOWS_FROM")";
+ }
+ writer << ',';
+
+ writer << R"("trace_id":)";
+ WriteId(writer, span_reference_data.trace_id);
+ writer << ',';
+ writer << R"("span_id":)";
+ WriteId(writer, span_reference_data.span_id);
+
+ writer << '}';
+}
+
+static void ToJson(std::ostream& writer, const Value& value);
+
+namespace {
+struct ValueVisitor {
+ std::ostream& writer;
+
+ void operator()(bool value) {
+ if (value) {
+ writer << "true";
+ } else {
+ writer << "false";
+ }
+ }
+
+ void operator()(double value) {
+ if (std::isnan(value)) {
+ writer << R"("NaN")";
+ } else if (std::isinf(value)) {
+ if (std::signbit(value)) {
+ writer << R"("-Inf")";
+ } else {
+ writer << R"("+Inf")";
+ }
+ } else {
+ writer << value;
+ }
+ }
+
+ void operator()(int64_t value) { writer << value; }
+
+ void operator()(uint64_t value) { writer << value; }
+
+ void operator()(const std::string& s) { WriteEscapedString(writer, s); }
+
+ void operator()(std::nullptr_t) { writer << "null"; }
+
+ void operator()(const char* s) { WriteEscapedString(writer, s); }
+
+ void operator()(const Values& values) {
+ writer << '[';
+ size_t i = 0;
+ for (const auto& value : values) {
+ ToJson(writer, value);
+ if (++i < values.size()) {
+ writer << ',';
+ }
+ }
+ writer << ']';
+ }
+
+ void operator()(const Dictionary& dictionary) {
+ writer << '{';
+ size_t i = 0;
+ for (const auto& key_value : dictionary) {
+ WriteEscapedString(writer, key_value.first);
+ writer << ':';
+ ToJson(writer, key_value.second);
+ if (++i < dictionary.size()) {
+ writer << ',';
+ }
+ }
+ writer << '}';
+ }
+};
+} // anonymous namespace
+
+void ToJson(std::ostream& writer, const Value& value) {
+ ValueVisitor value_visitor{writer};
+ apply_visitor(value_visitor, value);
+}
+
+template <class Rep, class Period>
+static void ToJson(std::ostream& writer,
+ const std::chrono::duration<Rep, Period>& duration) {
+ auto count =
+ std::chrono::duration_cast<std::chrono::microseconds>(duration).count();
+ writer << count;
+}
+
+static void ToJson(std::ostream& writer, const LogRecord& log_record) {
+ writer << '{';
+ writer << R"("timestamp":)";
+ ToJson(writer, log_record.timestamp.time_since_epoch());
+ writer << ',';
+ writer << R"("fields":)";
+ writer << '[';
+ auto num_fields = log_record.fields.size();
+ size_t field_index = 0;
+ for (auto& field : log_record.fields) {
+ writer << '{';
+ writer << R"("key":)";
+ WriteEscapedString(writer, field.first);
+ writer << ',';
+ writer << R"("value":)";
+ ToJson(writer, field.second);
+ writer << '}';
+ if (++field_index < num_fields) {
+ writer << ',';
+ }
+ }
+ writer << ']';
+ writer << '}';
+}
+
+static void ToJson(std::ostream& writer, const SpanData& span_data) {
+ writer << '{';
+
+ writer << R"("span_context":)";
+ ToJson(writer, span_data.span_context);
+ writer << ',';
+
+ writer << R"("references":)";
+ writer << '[';
+ auto num_references = span_data.references.size();
+ size_t reference_index = 0;
+ for (auto& reference : span_data.references) {
+ ToJson(writer, reference);
+ if (++reference_index < num_references) {
+ writer << ',';
+ }
+ }
+ writer << ']';
+ writer << ',';
+
+ writer << R"("operation_name":)";
+ WriteEscapedString(writer, span_data.operation_name);
+ writer << ',';
+
+ writer << R"("start_timestamp":)";
+ ToJson(writer, span_data.start_timestamp.time_since_epoch());
+ writer << ',';
+
+ writer << R"("duration":)";
+ ToJson(writer, span_data.duration);
+ writer << ',';
+
+ writer << R"("tags":)";
+ writer << '{';
+ auto num_tags = span_data.tags.size();
+ size_t tag_index = 0;
+ for (auto& tag : span_data.tags) {
+ WriteEscapedString(writer, tag.first);
+ writer << ':';
+ ToJson(writer, tag.second);
+ if (++tag_index < num_tags) {
+ writer << ',';
+ }
+ }
+ writer << '}';
+ writer << ',';
+
+ writer << R"("logs":)";
+ writer << '[';
+ auto num_logs = span_data.logs.size();
+ size_t log_index = 0;
+ for (auto& log : span_data.logs) {
+ ToJson(writer, log);
+ if (++log_index < num_logs) {
+ writer << ',';
+ }
+ }
+ writer << ']';
+
+ writer << '}';
+}
+
+void ToJson(std::ostream& writer, const std::vector<SpanData>& spans) {
+ writer << '[';
+ auto num_spans = spans.size();
+ size_t span_index = 0;
+ for (auto& span_data : spans) {
+ ToJson(writer, span_data);
+ if (++span_index < num_spans) {
+ writer << ',';
+ }
+ }
+ writer << ']';
+}
+} // namespace mocktracer
+END_OPENTRACING_ABI_NAMESPACE
+} // namespace opentracing
diff --git a/mocktracer/src/json_recorder.cpp b/mocktracer/src/json_recorder.cpp
new file mode 100644
index 0000000..5d30d9a
--- /dev/null
+++ b/mocktracer/src/json_recorder.cpp
@@ -0,0 +1,30 @@
+#include <opentracing/mocktracer/json.h>
+#include <opentracing/mocktracer/json_recorder.h>
+
+namespace opentracing {
+BEGIN_OPENTRACING_ABI_NAMESPACE
+namespace mocktracer {
+JsonRecorder::JsonRecorder(std::unique_ptr<std::ostream>&& out)
+ : out_{std::move(out)} {}
+
+void JsonRecorder::RecordSpan(SpanData&& span_data) noexcept try {
+ std::lock_guard<std::mutex> lock_guard{mutex_};
+ spans_.emplace_back(std::move(span_data));
+} catch (const std::exception&) {
+ // Drop span.
+}
+
+void JsonRecorder::Close() noexcept try {
+ if (out_ == nullptr) {
+ return;
+ }
+ std::lock_guard<std::mutex> lock_guard{mutex_};
+ ToJson(*out_, spans_);
+ out_->flush();
+ spans_.clear();
+} catch (const std::exception&) {
+ // Ignore errors.
+}
+} // namespace mocktracer
+END_OPENTRACING_ABI_NAMESPACE
+} // namespace opentracing
diff --git a/mocktracer/src/mock_span.cpp b/mocktracer/src/mock_span.cpp
new file mode 100644
index 0000000..b312bd8
--- /dev/null
+++ b/mocktracer/src/mock_span.cpp
@@ -0,0 +1,206 @@
+#include "mock_span.h"
+#include <cstdio>
+#include <random>
+
+namespace opentracing {
+BEGIN_OPENTRACING_ABI_NAMESPACE
+namespace mocktracer {
+
+static uint64_t GenerateId() {
+ static thread_local std::mt19937_64 rand_source{std::random_device()()};
+ return static_cast<uint64_t>(rand_source());
+}
+
+static std::tuple<SystemTime, SteadyTime> ComputeStartTimestamps(
+ const SystemTime& start_system_timestamp,
+ const SteadyTime& start_steady_timestamp) {
+ // If neither the system nor steady timestamps are set, get the tme from the
+ // respective clocks; otherwise, use the set timestamp to initialize the
+ // other.
+ if (start_system_timestamp == SystemTime() &&
+ start_steady_timestamp == SteadyTime()) {
+ return std::tuple<SystemTime, SteadyTime>{SystemClock::now(),
+ SteadyClock::now()};
+ }
+ if (start_system_timestamp == SystemTime()) {
+ return std::tuple<SystemTime, SteadyTime>{
+ opentracing::convert_time_point<SystemClock>(start_steady_timestamp),
+ start_steady_timestamp};
+ }
+ if (start_steady_timestamp == SteadyTime()) {
+ return std::tuple<SystemTime, SteadyTime>{
+ start_system_timestamp,
+ opentracing::convert_time_point<SteadyClock>(start_system_timestamp)};
+ }
+ return std::tuple<SystemTime, SteadyTime>{start_system_timestamp,
+ start_steady_timestamp};
+}
+
+static bool SetSpanReference(
+ const std::pair<SpanReferenceType, const SpanContext*>& reference,
+ std::map<std::string, std::string>& baggage,
+ SpanReferenceData& reference_data) {
+ reference_data.reference_type = reference.first;
+ if (reference.second == nullptr) {
+ return false;
+ }
+ auto referenced_context =
+ dynamic_cast<const MockSpanContext*>(reference.second);
+ if (referenced_context == nullptr) {
+ return false;
+ }
+ reference_data.trace_id = referenced_context->trace_id();
+ reference_data.span_id = referenced_context->span_id();
+
+ referenced_context->ForeachBaggageItem(
+ [&baggage](const std::string& key, const std::string& value) {
+ baggage[key] = value;
+ return true;
+ });
+
+ return true;
+}
+
+MockSpan::MockSpan(std::shared_ptr<const Tracer>&& tracer, Recorder* recorder,
+ string_view operation_name, const StartSpanOptions& options)
+ : tracer_{std::move(tracer)}, recorder_{recorder} {
+ data_.operation_name = operation_name;
+
+ // Set start timestamps
+ std::tie(data_.start_timestamp, start_steady_) = ComputeStartTimestamps(
+ options.start_system_timestamp, options.start_steady_timestamp);
+
+ // Set references
+ SpanContextData span_context_data;
+ for (auto& reference : options.references) {
+ SpanReferenceData reference_data;
+ if (!SetSpanReference(reference, span_context_data.baggage,
+ reference_data)) {
+ continue;
+ }
+ data_.references.push_back(reference_data);
+ }
+
+ // Set tags
+ for (auto& tag : options.tags) {
+ data_.tags[tag.first] = tag.second;
+ }
+
+ // Set span context
+ span_context_data.trace_id =
+ data_.references.empty() ? GenerateId() : data_.references[0].trace_id;
+ span_context_data.span_id = GenerateId();
+ span_context_ = MockSpanContext{std::move(span_context_data)};
+}
+
+MockSpan::~MockSpan() {
+ if (!is_finished_) {
+ Finish();
+ }
+}
+
+void MockSpan::FinishWithOptions(const FinishSpanOptions& options) noexcept {
+ // Ensure the span is only finished once.
+ if (is_finished_.exchange(true)) {
+ return;
+ }
+
+ data_.logs.reserve(data_.logs.size() + options.log_records.size());
+ for (auto& log_record : options.log_records) {
+ data_.logs.push_back(log_record);
+ }
+
+ auto finish_timestamp = options.finish_steady_timestamp;
+ if (finish_timestamp == SteadyTime{}) {
+ finish_timestamp = SteadyClock::now();
+ }
+
+ data_.duration = finish_timestamp - start_steady_;
+
+ span_context_.CopyData(data_.span_context);
+
+ if (recorder_ != nullptr) {
+ recorder_->RecordSpan(std::move(data_));
+ }
+}
+
+void MockSpan::SetOperationName(string_view name) noexcept try {
+ std::lock_guard<std::mutex> lock_guard{mutex_};
+ data_.operation_name = name;
+} catch (const std::exception& e) {
+ // Ignore operation
+ fprintf(stderr, "Failed to set operation name: %s\n", e.what());
+}
+
+void MockSpan::SetTag(string_view key,
+ const opentracing::Value& value) noexcept try {
+ std::lock_guard<std::mutex> lock_guard{mutex_};
+ data_.tags[key] = value;
+} catch (const std::exception& e) {
+ // Ignore upon error.
+ fprintf(stderr, "Failed to set tag: %s\n", e.what());
+}
+
+void MockSpan::Log(
+ std::initializer_list<std::pair<string_view, Value>> fields) noexcept {
+ Log(SystemClock::now(), fields);
+}
+
+void MockSpan::Log(
+ SystemTime timestamp,
+ std::initializer_list<std::pair<string_view, Value>> fields) noexcept try {
+ LogRecord log_record;
+ log_record.timestamp = timestamp;
+ log_record.fields.reserve(fields.size());
+ for (auto& field : fields) {
+ log_record.fields.emplace_back(field.first, field.second);
+ }
+ std::lock_guard<std::mutex> lock_guard{mutex_};
+ data_.logs.emplace_back(std::move(log_record));
+} catch (const std::exception& e) {
+ // Drop log record upon error.
+ fprintf(stderr, "Failed to log: %s\n", e.what());
+}
+
+void MockSpan::Log(
+ SystemTime timestamp,
+ const std::vector<std::pair<string_view, Value>>& fields) noexcept try {
+ LogRecord log_record;
+ log_record.timestamp = timestamp;
+ log_record.fields.reserve(fields.size());
+ for (auto& field : fields) {
+ log_record.fields.emplace_back(field.first, field.second);
+ }
+ std::lock_guard<std::mutex> lock_guard{mutex_};
+ data_.logs.emplace_back(std::move(log_record));
+} catch (const std::exception& e) {
+ // Drop log record upon error.
+ fprintf(stderr, "Failed to log: %s\n", e.what());
+}
+
+void MockSpan::SetBaggageItem(string_view restricted_key,
+ string_view value) noexcept try {
+ std::lock_guard<std::mutex> lock_guard{span_context_.baggage_mutex_};
+ span_context_.data_.baggage.emplace(restricted_key, value);
+} catch (const std::exception& e) {
+ // Drop baggage item upon error.
+ fprintf(stderr, "Failed to set baggage item: %s\n", e.what());
+}
+
+std::string MockSpan::BaggageItem(string_view restricted_key) const
+ noexcept try {
+ std::lock_guard<std::mutex> lock_guard{span_context_.baggage_mutex_};
+ auto lookup = span_context_.data_.baggage.find(restricted_key);
+ if (lookup != span_context_.data_.baggage.end()) {
+ return lookup->second;
+ }
+ return {};
+} catch (const std::exception& e) {
+ // Return empty string upon error.
+ fprintf(stderr, "Failed to retrieve baggage item: %s\n", e.what());
+ return {};
+}
+
+} // namespace mocktracer
+END_OPENTRACING_ABI_NAMESPACE
+} // namespace opentracing
diff --git a/mocktracer/src/mock_span.h b/mocktracer/src/mock_span.h
new file mode 100644
index 0000000..bf8ba05
--- /dev/null
+++ b/mocktracer/src/mock_span.h
@@ -0,0 +1,66 @@
+#ifndef OPENTRACING_MOCKTRACER_SPAN_H
+#define OPENTRACING_MOCKTRACER_SPAN_H
+
+#include <opentracing/mocktracer/tracer.h>
+#include <atomic>
+#include <mutex>
+#include "mock_span_context.h"
+
+namespace opentracing {
+BEGIN_OPENTRACING_ABI_NAMESPACE
+namespace mocktracer {
+
+class MockSpan : public Span {
+ public:
+ MockSpan(std::shared_ptr<const Tracer>&& tracer, Recorder* recorder,
+ string_view operation_name, const StartSpanOptions& options);
+
+ ~MockSpan() override;
+
+ void FinishWithOptions(const FinishSpanOptions& options) noexcept override;
+
+ void SetOperationName(string_view name) noexcept override;
+
+ void SetTag(string_view key,
+ const opentracing::Value& value) noexcept override;
+
+ void Log(std::initializer_list<std::pair<string_view, Value>>
+ fields) noexcept override;
+
+ void Log(SystemTime timestamp,
+ std::initializer_list<std::pair<string_view, Value>>
+ fields) noexcept override;
+
+ void Log(SystemTime timestamp,
+ const std::vector<std::pair<string_view, Value>>&
+ fields) noexcept override;
+
+ void SetBaggageItem(string_view restricted_key,
+ string_view value) noexcept override;
+
+ std::string BaggageItem(string_view restricted_key) const noexcept override;
+
+ const SpanContext& context() const noexcept override { return span_context_; }
+
+ const opentracing::Tracer& tracer() const noexcept override {
+ return *tracer_;
+ }
+
+ private:
+ std::shared_ptr<const Tracer> tracer_;
+ Recorder* recorder_;
+ MockSpanContext span_context_;
+ SteadyTime start_steady_;
+
+ std::atomic<bool> is_finished_{false};
+
+ // Mutex protects data_
+ std::mutex mutex_;
+ SpanData data_;
+};
+
+} // namespace mocktracer
+END_OPENTRACING_ABI_NAMESPACE
+} // namespace opentracing
+
+#endif // OPENTRACING_MOCKTRACER_SPAN_H
diff --git a/mocktracer/src/mock_span_context.cpp b/mocktracer/src/mock_span_context.cpp
new file mode 100644
index 0000000..ac6eb2b
--- /dev/null
+++ b/mocktracer/src/mock_span_context.cpp
@@ -0,0 +1,40 @@
+#include "mock_span_context.h"
+
+namespace opentracing {
+BEGIN_OPENTRACING_ABI_NAMESPACE
+namespace mocktracer {
+MockSpanContext& MockSpanContext::operator=(MockSpanContext&& other) noexcept {
+ data_.trace_id = other.data_.trace_id;
+ data_.span_id = other.data_.span_id;
+ data_.baggage = std::move(other.data_.baggage);
+ return *this;
+}
+
+void MockSpanContext::ForeachBaggageItem(
+ std::function<bool(const std::string& key, const std::string& value)> f)
+ const {
+ std::lock_guard<std::mutex> lock_guard{baggage_mutex_};
+ for (const auto& baggage_item : data_.baggage) {
+ if (!f(baggage_item.first, baggage_item.second)) {
+ return;
+ }
+ }
+}
+
+void MockSpanContext::CopyData(SpanContextData& data) const {
+ data.trace_id = data_.trace_id;
+ data.span_id = data_.span_id;
+ std::lock_guard<std::mutex> lock_guard{baggage_mutex_};
+ data.baggage = data_.baggage;
+}
+
+std::unique_ptr<SpanContext> MockSpanContext::Clone() const noexcept try {
+ auto result = std::unique_ptr<MockSpanContext>{new MockSpanContext{}};
+ CopyData(result->data_);
+ return std::unique_ptr<SpanContext>{result.release()};
+} catch (const std::exception& /*e*/) {
+ return nullptr;
+}
+} // namespace mocktracer
+END_OPENTRACING_ABI_NAMESPACE
+} // namespace opentracing
diff --git a/mocktracer/src/mock_span_context.h b/mocktracer/src/mock_span_context.h
new file mode 100644
index 0000000..ed156da
--- /dev/null
+++ b/mocktracer/src/mock_span_context.h
@@ -0,0 +1,79 @@
+#ifndef OPENTRACING_MOCKTRACER_SPAN_CONTEXT_H
+#define OPENTRACING_MOCKTRACER_SPAN_CONTEXT_H
+
+#include <opentracing/mocktracer/tracer.h>
+#include <exception>
+#include <mutex>
+#include <string>
+#include "propagation.h"
+
+namespace opentracing {
+BEGIN_OPENTRACING_ABI_NAMESPACE
+namespace mocktracer {
+
+class MockSpan;
+
+class MockSpanContext : public SpanContext {
+ public:
+ MockSpanContext() = default;
+
+ MockSpanContext(SpanContextData&& data) noexcept : data_(std::move(data)) {}
+
+ MockSpanContext(const MockSpanContext&) = delete;
+ MockSpanContext(MockSpanContext&&) = delete;
+
+ ~MockSpanContext() override = default;
+
+ MockSpanContext& operator=(const MockSpanContext&) = delete;
+ MockSpanContext& operator=(MockSpanContext&& other) noexcept;
+
+ void ForeachBaggageItem(
+ std::function<bool(const std::string& key, const std::string& value)> f)
+ const override;
+
+ std::string ToTraceID() const noexcept override try {
+ return std::to_string(data_.trace_id);
+ } catch (const std::exception& /*e*/) {
+ return {};
+ }
+
+ std::string ToSpanID() const noexcept override try {
+ return std::to_string(data_.span_id);
+ } catch (const std::exception& /*e*/) {
+ return {};
+ }
+
+ uint64_t trace_id() const noexcept { return data_.trace_id; }
+
+ uint64_t span_id() const noexcept { return data_.span_id; }
+
+ void CopyData(SpanContextData& data) const;
+
+ template <class Carrier>
+ expected<void> Inject(const PropagationOptions& propagation_options,
+ Carrier& writer) const {
+ std::lock_guard<std::mutex> lock_guard{baggage_mutex_};
+ return InjectSpanContext(propagation_options, writer, data_);
+ }
+
+ template <class Carrier>
+ expected<bool> Extract(const PropagationOptions& propagation_options,
+ Carrier& reader) {
+ std::lock_guard<std::mutex> lock_guard{baggage_mutex_};
+ return ExtractSpanContext(propagation_options, reader, data_);
+ }
+
+ std::unique_ptr<SpanContext> Clone() const noexcept override;
+
+ private:
+ friend MockSpan;
+
+ mutable std::mutex baggage_mutex_;
+ SpanContextData data_;
+};
+
+} // namespace mocktracer
+END_OPENTRACING_ABI_NAMESPACE
+} // namespace opentracing
+
+#endif // OPENTRACING_MOCKTRACER_SPAN_CONTEXT_H
diff --git a/mocktracer/src/propagation.cpp b/mocktracer/src/propagation.cpp
new file mode 100644
index 0000000..d546877
--- /dev/null
+++ b/mocktracer/src/propagation.cpp
@@ -0,0 +1,214 @@
+#include "propagation.h"
+#include <algorithm>
+#include <cctype>
+#include <functional>
+#include <iostream>
+#include <sstream>
+#include "base64.h"
+#include "utility.h"
+
+namespace opentracing {
+BEGIN_OPENTRACING_ABI_NAMESPACE
+namespace mocktracer {
+static void WriteString(std::ostream& ostream, const std::string& s) {
+ const uint32_t size = static_cast<uint32_t>(s.size());
+ ostream.write(reinterpret_cast<const char*>(&size), sizeof(size));
+ ostream.write(s.data(), size);
+}
+
+static void ReadString(std::istream& istream, std::string& s) {
+ uint32_t size = 0;
+ istream.read(reinterpret_cast<char*>(&size), sizeof(size));
+ s.resize(size);
+ istream.read(&s[0], size);
+}
+
+expected<void> InjectSpanContext(
+ const PropagationOptions& /*propagation_options*/, std::ostream& carrier,
+ const SpanContextData& span_context_data) {
+ auto trace_id = SwapEndianIfBig(span_context_data.trace_id);
+ carrier.write(reinterpret_cast<const char*>(&trace_id), sizeof(trace_id));
+ auto span_id = SwapEndianIfBig(span_context_data.span_id);
+ carrier.write(reinterpret_cast<const char*>(&span_id), sizeof(span_id));
+
+ const uint32_t num_baggage =
+ SwapEndianIfBig(static_cast<uint32_t>(span_context_data.baggage.size()));
+ carrier.write(reinterpret_cast<const char*>(&num_baggage),
+ sizeof(num_baggage));
+ for (auto& baggage_item : span_context_data.baggage) {
+ WriteString(carrier, baggage_item.first);
+ WriteString(carrier, baggage_item.second);
+ }
+
+ // Flush so that when we call carrier.good(), we'll get an accurate view of
+ // the error state.
+ carrier.flush();
+ if (!carrier.good()) {
+ return opentracing::make_unexpected(
+ std::make_error_code(std::errc::io_error));
+ }
+
+ return {};
+}
+
+expected<bool> ExtractSpanContext(
+ const PropagationOptions& /*propagation_options*/, std::istream& carrier,
+ SpanContextData& span_context_data) try {
+ // istream::peek returns EOF if it's in an error state, so check for an error
+ // state first before checking for an empty stream.
+ if (!carrier.good()) {
+ return opentracing::make_unexpected(
+ std::make_error_code(std::errc::io_error));
+ }
+
+ // Check for the case when no span is encoded.
+ if (carrier.peek() == EOF) {
+ return false;
+ }
+ carrier.read(reinterpret_cast<char*>(&span_context_data.trace_id),
+ sizeof(span_context_data.trace_id));
+ span_context_data.trace_id = SwapEndianIfBig(span_context_data.trace_id);
+ carrier.read(reinterpret_cast<char*>(&span_context_data.span_id),
+ sizeof(span_context_data.span_id));
+ span_context_data.span_id = SwapEndianIfBig(span_context_data.span_id);
+ uint32_t num_baggage = 0;
+ carrier.read(reinterpret_cast<char*>(&num_baggage), sizeof(num_baggage));
+ num_baggage = SwapEndianIfBig(num_baggage);
+ std::string baggage_key, baggage_value;
+ for (int i = 0; i < static_cast<int>(num_baggage); ++i) {
+ ReadString(carrier, baggage_key);
+ ReadString(carrier, baggage_value);
+ span_context_data.baggage[baggage_key] = baggage_value;
+ if (!carrier.good()) {
+ return opentracing::make_unexpected(
+ std::make_error_code(std::errc::io_error));
+ }
+ }
+
+ return true;
+} catch (const std::bad_alloc&) {
+ return opentracing::make_unexpected(
+ std::make_error_code(std::errc::not_enough_memory));
+}
+
+expected<void> InjectSpanContext(const PropagationOptions& propagation_options,
+ const TextMapWriter& carrier,
+ const SpanContextData& span_context_data) {
+ std::ostringstream ostream;
+ auto result =
+ InjectSpanContext(propagation_options, ostream, span_context_data);
+ if (!result) {
+ return result;
+ }
+ std::string context_value;
+ try {
+ auto binary_encoding = ostream.str();
+ context_value =
+ Base64::encode(binary_encoding.data(), binary_encoding.size());
+ } catch (const std::bad_alloc&) {
+ return opentracing::make_unexpected(
+ std::make_error_code(std::errc::not_enough_memory));
+ }
+
+ result = carrier.Set(propagation_options.propagation_key, context_value);
+ if (!result) {
+ return result;
+ }
+ return {};
+}
+
+template <class KeyCompare>
+static opentracing::expected<opentracing::string_view> LookupKey(
+ const opentracing::TextMapReader& carrier, opentracing::string_view key,
+ KeyCompare key_compare) {
+ // First try carrier.LookupKey since that can potentially be the fastest
+ // approach.
+ auto result = carrier.LookupKey(key);
+ if (result ||
+ !are_errors_equal(result.error(),
+ opentracing::lookup_key_not_supported_error)) {
+ return result;
+ }
+
+ // Fall back to iterating through all of the keys.
+ result = opentracing::make_unexpected(opentracing::key_not_found_error);
+ auto was_successful = carrier.ForeachKey(
+ [&](opentracing::string_view carrier_key,
+ opentracing::string_view value) -> opentracing::expected<void> {
+ if (!key_compare(carrier_key, key)) {
+ return {};
+ }
+ result = value;
+
+ // Found key, so bail out of the loop with a success error code.
+ return opentracing::make_unexpected(std::error_code{});
+ });
+ if (!was_successful && was_successful.error() != std::error_code{}) {
+ return opentracing::make_unexpected(was_successful.error());
+ }
+ return result;
+}
+
+template <class KeyCompare>
+static opentracing::expected<bool> ExtractSpanContext(
+ const PropagationOptions& propagation_options,
+ const opentracing::TextMapReader& carrier,
+ SpanContextData& span_context_data, KeyCompare key_compare) {
+ auto value_maybe =
+ LookupKey(carrier, propagation_options.propagation_key, key_compare);
+ if (!value_maybe) {
+ if (are_errors_equal(value_maybe.error(),
+ opentracing::key_not_found_error)) {
+ return false;
+ } else {
+ return opentracing::make_unexpected(value_maybe.error());
+ }
+ }
+ auto value = *value_maybe;
+ std::string base64_decoding;
+ try {
+ base64_decoding = Base64::decode(value.data(), value.size());
+ } catch (const std::bad_alloc&) {
+ return opentracing::make_unexpected(
+ std::make_error_code(std::errc::not_enough_memory));
+ }
+ if (base64_decoding.empty()) {
+ return opentracing::make_unexpected(
+ opentracing::span_context_corrupted_error);
+ }
+ std::istringstream istream{base64_decoding};
+ return ExtractSpanContext(propagation_options, istream, span_context_data);
+}
+
+expected<bool> ExtractSpanContext(const PropagationOptions& propagation_options,
+ const TextMapReader& carrier,
+ SpanContextData& span_context_data) {
+ return ExtractSpanContext(propagation_options, carrier, span_context_data,
+ std::equal_to<string_view>{});
+}
+
+expected<void> InjectSpanContext(const PropagationOptions& propagation_options,
+ const HTTPHeadersWriter& carrier,
+ const SpanContextData& span_context_data) {
+ return InjectSpanContext(propagation_options,
+ static_cast<const TextMapWriter&>(carrier),
+ span_context_data);
+}
+
+expected<bool> ExtractSpanContext(const PropagationOptions& propagation_options,
+ const HTTPHeadersReader& carrier,
+ SpanContextData& span_context_data) {
+ auto iequals = [](opentracing::string_view lhs,
+ opentracing::string_view rhs) {
+ return lhs.length() == rhs.length() &&
+ std::equal(std::begin(lhs), std::end(lhs), std::begin(rhs),
+ [](char a, char b) {
+ return std::tolower(a) == std::tolower(b);
+ });
+ };
+ return ExtractSpanContext(propagation_options, carrier, span_context_data,
+ iequals);
+}
+} // namespace mocktracer
+END_OPENTRACING_ABI_NAMESPACE
+} // namespace opentracing
diff --git a/mocktracer/src/propagation.h b/mocktracer/src/propagation.h
new file mode 100644
index 0000000..6dced93
--- /dev/null
+++ b/mocktracer/src/propagation.h
@@ -0,0 +1,39 @@
+#ifndef OPENTRACING_MOCKTRACER_PROPAGATION_H
+#define OPENTRACING_MOCKTRACER_PROPAGATION_H
+
+#include <opentracing/mocktracer/recorder.h>
+#include <opentracing/mocktracer/tracer.h>
+#include <opentracing/propagation.h>
+
+namespace opentracing {
+BEGIN_OPENTRACING_ABI_NAMESPACE
+namespace mocktracer {
+
+expected<void> InjectSpanContext(const PropagationOptions& propagation_options,
+ std::ostream& carrier,
+ const SpanContextData& span_context_data);
+
+expected<bool> ExtractSpanContext(const PropagationOptions& propagation_options,
+ std::istream& carrier,
+ SpanContextData& span_context_data);
+
+expected<void> InjectSpanContext(const PropagationOptions& propagation_options,
+ const TextMapWriter& carrier,
+ const SpanContextData& span_context_data);
+
+expected<bool> ExtractSpanContext(const PropagationOptions& propagation_options,
+ const TextMapReader& carrier,
+ SpanContextData& span_context_data);
+
+expected<void> InjectSpanContext(const PropagationOptions& propagation_options,
+ const HTTPHeadersWriter& carrier,
+ const SpanContextData& span_context_data);
+
+expected<bool> ExtractSpanContext(const PropagationOptions& propagation_options,
+ const HTTPHeadersReader& carrier,
+ SpanContextData& span_context_data);
+} // namespace mocktracer
+END_OPENTRACING_ABI_NAMESPACE
+} // namespace opentracing
+
+#endif // OPENTRACING_MOCKTRACER_PROPAGATION_H
diff --git a/mocktracer/src/tracer.cpp b/mocktracer/src/tracer.cpp
new file mode 100644
index 0000000..e5f9127
--- /dev/null
+++ b/mocktracer/src/tracer.cpp
@@ -0,0 +1,109 @@
+#include <opentracing/mocktracer/tracer.h>
+#include <cstdio>
+#include <exception>
+
+#include "mock_span.h"
+#include "mock_span_context.h"
+#include "propagation.h"
+
+#include <opentracing/dynamic_load.h>
+#include <opentracing/mocktracer/tracer_factory.h>
+#include <cstdio>
+#include <cstring>
+#include <exception>
+
+namespace opentracing {
+BEGIN_OPENTRACING_ABI_NAMESPACE
+namespace mocktracer {
+
+template <class Carrier>
+static expected<void> InjectImpl(const PropagationOptions& propagation_options,
+ const opentracing::SpanContext& span_context,
+ Carrier& writer) {
+ if (propagation_options.inject_error_code.value() != 0) {
+ return opentracing::make_unexpected(propagation_options.inject_error_code);
+ }
+ auto mock_span_context = dynamic_cast<const MockSpanContext*>(&span_context);
+ if (mock_span_context == nullptr) {
+ return opentracing::make_unexpected(
+ opentracing::invalid_span_context_error);
+ }
+ return mock_span_context->Inject(propagation_options, writer);
+}
+
+template <class Carrier>
+opentracing::expected<std::unique_ptr<opentracing::SpanContext>> ExtractImpl(
+ const PropagationOptions& propagation_options, Carrier& reader) {
+ if (propagation_options.extract_error_code.value() != 0) {
+ return opentracing::make_unexpected(propagation_options.extract_error_code);
+ }
+ MockSpanContext* mock_span_context;
+ try {
+ mock_span_context = new MockSpanContext{};
+ } catch (const std::bad_alloc&) {
+ return opentracing::make_unexpected(
+ make_error_code(std::errc::not_enough_memory));
+ }
+ std::unique_ptr<opentracing::SpanContext> span_context(mock_span_context);
+ auto result = mock_span_context->Extract(propagation_options, reader);
+ if (!result) {
+ return opentracing::make_unexpected(result.error());
+ }
+ if (!*result) {
+ span_context.reset();
+ }
+ return std::move(span_context);
+}
+
+MockTracer::MockTracer(MockTracerOptions&& options)
+ : recorder_{std::move(options.recorder)},
+ propagation_options_{std::move(options.propagation_options)} {}
+
+std::unique_ptr<Span> MockTracer::StartSpanWithOptions(
+ string_view operation_name, const StartSpanOptions& options) const
+ noexcept try {
+ return std::unique_ptr<Span>{new MockSpan{shared_from_this(), recorder_.get(),
+ operation_name, options}};
+} catch (const std::exception& e) {
+ fprintf(stderr, "Failed to start span: %s\n", e.what());
+ return nullptr;
+}
+
+void MockTracer::Close() noexcept {
+ if (recorder_ != nullptr) {
+ recorder_->Close();
+ }
+}
+
+expected<void> MockTracer::Inject(const SpanContext& sc,
+ std::ostream& writer) const {
+ return InjectImpl(propagation_options_, sc, writer);
+}
+
+expected<void> MockTracer::Inject(const SpanContext& sc,
+ const TextMapWriter& writer) const {
+ return InjectImpl(propagation_options_, sc, writer);
+}
+
+expected<void> MockTracer::Inject(const SpanContext& sc,
+ const HTTPHeadersWriter& writer) const {
+ return InjectImpl(propagation_options_, sc, writer);
+}
+
+expected<std::unique_ptr<SpanContext>> MockTracer::Extract(
+ std::istream& reader) const {
+ return ExtractImpl(propagation_options_, reader);
+}
+
+expected<std::unique_ptr<SpanContext>> MockTracer::Extract(
+ const TextMapReader& reader) const {
+ return ExtractImpl(propagation_options_, reader);
+}
+
+expected<std::unique_ptr<SpanContext>> MockTracer::Extract(
+ const HTTPHeadersReader& reader) const {
+ return ExtractImpl(propagation_options_, reader);
+}
+} // namespace mocktracer
+END_OPENTRACING_ABI_NAMESPACE
+} // namespace opentracing
diff --git a/mocktracer/src/tracer_factory.cpp b/mocktracer/src/tracer_factory.cpp
new file mode 100644
index 0000000..ebfbe60
--- /dev/null
+++ b/mocktracer/src/tracer_factory.cpp
@@ -0,0 +1,139 @@
+#include <opentracing/mocktracer/json_recorder.h>
+#include <opentracing/mocktracer/tracer.h>
+#include <opentracing/mocktracer/tracer_factory.h>
+#include <cctype>
+#include <cerrno>
+#include <cstring>
+#include <fstream>
+#include <stdexcept>
+#include <string>
+
+namespace opentracing {
+BEGIN_OPENTRACING_ABI_NAMESPACE
+namespace mocktracer {
+
+namespace {
+struct InvalidConfigurationError : public std::exception {
+ public:
+ InvalidConfigurationError(const char* position, std::string&& message)
+ : position_{position}, message_{std::move(message)} {}
+
+ const char* what() const noexcept override { return message_.c_str(); }
+
+ const char* position() const { return position_; }
+
+ private:
+ const char* position_;
+ std::string message_;
+};
+} // namespace
+
+static void Consume(const char*& i, const char* last, string_view s) {
+ if (static_cast<std::size_t>(std::distance(i, last)) < s.size()) {
+ throw InvalidConfigurationError{i,
+ std::string{"expected "} + std::string{s}};
+ }
+
+ for (size_t index = 0; index < s.size(); ++index) {
+ if (*i++ != s[index]) {
+ throw InvalidConfigurationError{
+ i, std::string{"expected "} +
+ std::string{s.data() + index, s.data() + s.size()}};
+ }
+ }
+}
+
+static void ConsumeWhitespace(const char*& i, const char* last) {
+ for (; i != last; ++i) {
+ if (!std::isspace(*i)) {
+ return;
+ }
+ }
+}
+
+static void ConsumeToken(const char*& i, const char* last, string_view token) {
+ ConsumeWhitespace(i, last);
+ Consume(i, last, token);
+}
+
+static std::string ParseFilename(const char*& i, const char* last) {
+ ConsumeToken(i, last, "\"");
+ std::string result;
+ while (i != last) {
+ if (*i == '\"') {
+ ++i;
+ return result;
+ }
+ if (*i == '\\') {
+ throw InvalidConfigurationError{
+ i, "escaped characters are not supported in filename"};
+ }
+ if (std::isprint(*i)) {
+ result.push_back(*i);
+ } else {
+ throw InvalidConfigurationError{i, "invalid character"};
+ }
+ ++i;
+ }
+
+ throw InvalidConfigurationError{i, R"(no matching ")"};
+}
+
+static std::string ParseConfiguration(const char* i, const char* last) {
+ ConsumeToken(i, last, "{");
+ ConsumeToken(i, last, R"("output_file")");
+ ConsumeToken(i, last, ":");
+ auto filename = ParseFilename(i, last);
+ ConsumeToken(i, last, "}");
+ ConsumeWhitespace(i, last);
+ if (i != last) {
+ throw InvalidConfigurationError{i, "expected EOF"};
+ }
+
+ return filename;
+}
+
+struct MockTracerConfiguration {
+ std::string output_file;
+};
+
+expected<std::shared_ptr<Tracer>> MockTracerFactory::MakeTracer(
+ const char* configuration, std::string& error_message) const noexcept try {
+ MockTracerConfiguration tracer_configuration;
+ if (configuration == nullptr) {
+ error_message = "configuration must not be null";
+ return make_unexpected(invalid_configuration_error);
+ }
+ try {
+ tracer_configuration.output_file = ParseConfiguration(
+ configuration, configuration + std::strlen(configuration));
+ } catch (const InvalidConfigurationError& e) {
+ error_message = std::string{"Error parsing configuration at position "} +
+ std::to_string(std::distance(configuration, e.position())) +
+ ": " + e.what();
+ return make_unexpected(invalid_configuration_error);
+ }
+
+ errno = 0;
+ std::unique_ptr<std::ostream> ostream{
+ new std::ofstream{tracer_configuration.output_file}};
+ if (!ostream->good()) {
+ error_message = "failed to open file `";
+ error_message += tracer_configuration.output_file + "` (";
+ error_message += std::strerror(errno);
+ error_message += ")";
+ return make_unexpected(invalid_configuration_error);
+ }
+
+ MockTracerOptions tracer_options;
+ tracer_options.recorder =
+ std::unique_ptr<Recorder>{new JsonRecorder{std::move(ostream)}};
+
+ return std::shared_ptr<Tracer>{new MockTracer{std::move(tracer_options)}};
+} catch (const std::bad_alloc&) {
+ return make_unexpected(std::make_error_code(std::errc::not_enough_memory));
+}
+
+} // namespace mocktracer
+END_OPENTRACING_ABI_NAMESPACE
+} // namespace opentracing
diff --git a/mocktracer/src/utility.cpp b/mocktracer/src/utility.cpp
new file mode 100644
index 0000000..4a08d82
--- /dev/null
+++ b/mocktracer/src/utility.cpp
@@ -0,0 +1,57 @@
+#include "utility.h"
+#include <climits>
+
+namespace opentracing {
+BEGIN_OPENTRACING_ABI_NAMESPACE
+namespace mocktracer {
+
+// Converts swaps the endianness of a number.
+//
+// Taken from https://stackoverflow.com/a/105339/4447365
+template <typename T>
+static T SwapEndian(T u) {
+ static_assert(CHAR_BIT == 8, "CHAR_BIT != 8");
+
+ union {
+ T u;
+ unsigned char u8[sizeof(T)];
+ } source, dest;
+
+ source.u = u;
+
+ for (size_t k = 0; k < sizeof(T); k++)
+ dest.u8[k] = source.u8[sizeof(T) - k - 1];
+
+ return dest.u;
+}
+
+// Determines whether the architecture is big endian.
+//
+// Taken from https://stackoverflow.com/a/1001373/4447365
+static bool IsBigEndian() {
+ union {
+ uint32_t i;
+ char c[4];
+ } bint = {0x01020304};
+
+ return bint.c[0] == 1;
+}
+
+uint64_t SwapEndianIfBig(uint64_t x) {
+ if (IsBigEndian()) {
+ return SwapEndian(x);
+ } else {
+ return x;
+ }
+}
+
+uint32_t SwapEndianIfBig(uint32_t x) {
+ if (IsBigEndian()) {
+ return SwapEndian(x);
+ } else {
+ return x;
+ }
+}
+} // namespace mocktracer
+END_OPENTRACING_ABI_NAMESPACE
+} // namespace opentracing
diff --git a/mocktracer/src/utility.h b/mocktracer/src/utility.h
new file mode 100644
index 0000000..f7cff91
--- /dev/null
+++ b/mocktracer/src/utility.h
@@ -0,0 +1,17 @@
+#ifndef OPENTRACING_MOCKTRACER_UTILITY_H
+#define OPENTRACING_MOCKTRACER_UTILITY_H
+
+#include <opentracing/mocktracer/tracer.h>
+#include <cstdint>
+
+namespace opentracing {
+BEGIN_OPENTRACING_ABI_NAMESPACE
+namespace mocktracer {
+// If the native architecture is big endian, swaps the endianness of x
+uint64_t SwapEndianIfBig(uint64_t x);
+uint32_t SwapEndianIfBig(uint32_t x);
+} // namespace mocktracer
+END_OPENTRACING_ABI_NAMESPACE
+} // namespace opentracing
+
+#endif // OPENTRACING_MOCKTRACER_UTILITY_H