summaryrefslogtreecommitdiffstats
path: root/mocktracer
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--mocktracer/BUILD19
-rw-r--r--mocktracer/CMakeLists.txt52
-rw-r--r--mocktracer/LICENSE.apache202
-rw-r--r--mocktracer/include/opentracing/mocktracer/in_memory_recorder.h34
-rw-r--r--mocktracer/include/opentracing/mocktracer/json.h18
-rw-r--r--mocktracer/include/opentracing/mocktracer/json_recorder.h35
-rw-r--r--mocktracer/include/opentracing/mocktracer/recorder.h87
-rw-r--r--mocktracer/include/opentracing/mocktracer/symbols.h21
-rw-r--r--mocktracer/include/opentracing/mocktracer/tracer.h86
-rw-r--r--mocktracer/include/opentracing/mocktracer/tracer_factory.h22
-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
-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
32 files changed, 2757 insertions, 0 deletions
diff --git a/mocktracer/BUILD b/mocktracer/BUILD
new file mode 100644
index 0000000..3b22bab
--- /dev/null
+++ b/mocktracer/BUILD
@@ -0,0 +1,19 @@
+cc_library(
+ name = "mocktracer",
+ srcs = glob(["src/*.cpp", "src/*.h"]),
+ hdrs = glob(["include/opentracing/**/*.h"]),
+ strip_include_prefix = "include",
+ visibility = ["//visibility:public"],
+ deps = [
+ "//:opentracing",
+ ],
+)
+
+cc_binary(
+ name = "libmocktracer_plugin.so",
+ linkshared = 1,
+ visibility = ["//visibility:public"],
+ deps = [
+ "//mocktracer:mocktracer"
+ ],
+)
diff --git a/mocktracer/CMakeLists.txt b/mocktracer/CMakeLists.txt
new file mode 100644
index 0000000..9835fe3
--- /dev/null
+++ b/mocktracer/CMakeLists.txt
@@ -0,0 +1,52 @@
+include_directories(include)
+
+set(SRCS src/mock_span_context.cpp
+ src/mock_span.cpp
+ src/in_memory_recorder.cpp
+ src/json_recorder.cpp
+ src/base64.cpp
+ src/propagation.cpp
+ src/utility.cpp
+ src/json.cpp
+ src/tracer.cpp
+ src/tracer_factory.cpp)
+
+if (BUILD_SHARED_LIBS)
+ add_library(opentracing_mocktracer SHARED ${SRCS} src/dynamic_load.cpp)
+ target_include_directories(opentracing_mocktracer INTERFACE "$<INSTALL_INTERFACE:include/>")
+ set_target_properties(opentracing_mocktracer PROPERTIES VERSION ${OPENTRACING_VERSION_STRING}
+ SOVERSION ${OPENTRACING_VERSION_MAJOR})
+ target_link_libraries(opentracing_mocktracer PUBLIC opentracing)
+ target_compile_definitions(opentracing_mocktracer PRIVATE OPENTRACING_MOCK_TRACER_EXPORTS)
+ install(TARGETS opentracing_mocktracer
+ COMPONENT DIST
+ EXPORT OpenTracingTargets
+ LIBRARY DESTINATION ${LIB_INSTALL_DIR}
+ ARCHIVE DESTINATION ${LIB_INSTALL_DIR})
+
+
+endif()
+
+if (BUILD_STATIC_LIBS)
+ add_library(opentracing_mocktracer-static STATIC ${SRCS})
+ # Windows generates a lib and dll files for a shared library. using the same name will override the lib file generated by the shared target
+ if (NOT WIN32)
+ set_target_properties(opentracing_mocktracer-static PROPERTIES OUTPUT_NAME opentracing_mocktracer)
+ endif()
+ target_compile_definitions(opentracing_mocktracer-static PUBLIC OPENTRACING_MOCK_TRACER_STATIC)
+ target_include_directories(opentracing_mocktracer-static INTERFACE "$<INSTALL_INTERFACE:include/>")
+ target_link_libraries(opentracing_mocktracer-static opentracing-static)
+ install(TARGETS opentracing_mocktracer-static EXPORT OpenTracingTargets
+ ARCHIVE DESTINATION ${LIB_INSTALL_DIR})
+endif()
+
+install(DIRECTORY include/opentracing DESTINATION include
+ FILES_MATCHING PATTERN "*.h")
+
+# ==============================================================================
+# Testing
+
+include(CTest)
+if(BUILD_TESTING)
+ add_subdirectory(test)
+endif()
diff --git a/mocktracer/LICENSE.apache b/mocktracer/LICENSE.apache
new file mode 100644
index 0000000..760a01d
--- /dev/null
+++ b/mocktracer/LICENSE.apache
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner].
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License. \ No newline at end of file
diff --git a/mocktracer/include/opentracing/mocktracer/in_memory_recorder.h b/mocktracer/include/opentracing/mocktracer/in_memory_recorder.h
new file mode 100644
index 0000000..31ff4df
--- /dev/null
+++ b/mocktracer/include/opentracing/mocktracer/in_memory_recorder.h
@@ -0,0 +1,34 @@
+#ifndef OPENTRACING_MOCKTRACER_IN_MEMORY_RECORDER_H
+#define OPENTRACING_MOCKTRACER_IN_MEMORY_RECORDER_H
+
+#include <opentracing/mocktracer/recorder.h>
+#include <opentracing/mocktracer/symbols.h>
+#include <mutex>
+#include <vector>
+
+namespace opentracing {
+BEGIN_OPENTRACING_ABI_NAMESPACE
+namespace mocktracer {
+// InMemoryRecorder stores finished spans and provides accessors to them.
+class OPENTRACING_MOCK_TRACER_API InMemoryRecorder : public Recorder {
+ public:
+ void RecordSpan(SpanData&& span_data) noexcept override;
+
+ // Returns a vector of all finished spans.
+ std::vector<SpanData> spans() const;
+
+ // Returns the number of finished spans.
+ size_t size() const;
+
+ // Returns the last finished span. Throws if no spans have been finished.
+ SpanData top() const;
+
+ private:
+ mutable std::mutex mutex_;
+ std::vector<SpanData> spans_;
+};
+} // namespace mocktracer
+END_OPENTRACING_ABI_NAMESPACE
+} // namespace opentracing
+
+#endif // OPENTRACING_MOCKTRACER_IN_MEMORY_RECORDER_H
diff --git a/mocktracer/include/opentracing/mocktracer/json.h b/mocktracer/include/opentracing/mocktracer/json.h
new file mode 100644
index 0000000..0d35e90
--- /dev/null
+++ b/mocktracer/include/opentracing/mocktracer/json.h
@@ -0,0 +1,18 @@
+#ifndef OPENTRACING_MOCKTRACER_JSON_H
+#define OPENTRACING_MOCKTRACER_JSON_H
+
+#include <opentracing/mocktracer/recorder.h>
+#include <opentracing/mocktracer/symbols.h>
+#include <vector>
+
+namespace opentracing {
+BEGIN_OPENTRACING_ABI_NAMESPACE
+namespace mocktracer {
+// Serialize provided spans to JSON.
+OPENTRACING_MOCK_TRACER_API void ToJson(std::ostream& writer,
+ const std::vector<SpanData>& spans);
+} // namespace mocktracer
+END_OPENTRACING_ABI_NAMESPACE
+} // namespace opentracing
+
+#endif // OPENTRACING_MOCKTRACER_JSON_H
diff --git a/mocktracer/include/opentracing/mocktracer/json_recorder.h b/mocktracer/include/opentracing/mocktracer/json_recorder.h
new file mode 100644
index 0000000..fa5ef3c
--- /dev/null
+++ b/mocktracer/include/opentracing/mocktracer/json_recorder.h
@@ -0,0 +1,35 @@
+#ifndef OPENTRACING_MOCKTRACER_JSON_RECORDER_H
+#define OPENTRACING_MOCKTRACER_JSON_RECORDER_H
+
+#include <opentracing/mocktracer/recorder.h>
+#include <opentracing/mocktracer/symbols.h>
+#include <iosfwd>
+#include <memory>
+#include <mutex>
+#include <vector>
+
+namespace opentracing {
+BEGIN_OPENTRACING_ABI_NAMESPACE
+namespace mocktracer {
+// JsonRecorder serializes finished spans to a provided std::ostream in a JSON
+// format.
+//
+// See also FromJson.
+class OPENTRACING_MOCK_TRACER_API JsonRecorder : public Recorder {
+ public:
+ explicit JsonRecorder(std::unique_ptr<std::ostream>&& out);
+
+ void RecordSpan(SpanData&& span_data) noexcept override;
+
+ void Close() noexcept override;
+
+ private:
+ std::mutex mutex_;
+ std::unique_ptr<std::ostream> out_;
+ std::vector<SpanData> spans_;
+};
+} // namespace mocktracer
+END_OPENTRACING_ABI_NAMESPACE
+} // namespace opentracing
+
+#endif // OPENTRACING_MOCKTRACER_JSON_RECORDER_H
diff --git a/mocktracer/include/opentracing/mocktracer/recorder.h b/mocktracer/include/opentracing/mocktracer/recorder.h
new file mode 100644
index 0000000..d6b8554
--- /dev/null
+++ b/mocktracer/include/opentracing/mocktracer/recorder.h
@@ -0,0 +1,87 @@
+#ifndef OPENTRACING_MOCKTRACER_RECORDER_H
+#define OPENTRACING_MOCKTRACER_RECORDER_H
+
+#include <opentracing/mocktracer/symbols.h>
+#include <opentracing/tracer.h>
+
+#include <cstdint>
+#include <iosfwd>
+#include <map>
+
+namespace opentracing {
+BEGIN_OPENTRACING_ABI_NAMESPACE
+namespace mocktracer {
+struct SpanContextData {
+ uint64_t trace_id;
+ uint64_t span_id;
+ std::map<std::string, std::string> baggage;
+};
+
+inline bool operator==(const SpanContextData& lhs, const SpanContextData& rhs) {
+ return lhs.trace_id == rhs.trace_id && lhs.span_id == rhs.span_id &&
+ lhs.baggage == rhs.baggage;
+}
+
+inline bool operator!=(const SpanContextData& lhs, const SpanContextData& rhs) {
+ return !(lhs == rhs);
+}
+
+std::ostream& operator<<(std::ostream& out,
+ const SpanContextData& span_context_data);
+
+struct SpanReferenceData {
+ SpanReferenceType reference_type;
+ uint64_t trace_id;
+ uint64_t span_id;
+};
+
+inline bool operator==(const SpanReferenceData& lhs,
+ const SpanReferenceData& rhs) {
+ return lhs.reference_type == rhs.reference_type &&
+ lhs.trace_id == rhs.trace_id && lhs.span_id == rhs.span_id;
+}
+
+inline bool operator!=(const SpanReferenceData& lhs,
+ const SpanReferenceData& rhs) {
+ return !(lhs == rhs);
+}
+
+struct SpanData {
+ SpanContextData span_context;
+ std::vector<SpanReferenceData> references;
+ std::string operation_name;
+ SystemTime start_timestamp;
+ SteadyClock::duration duration;
+ std::map<std::string, Value> tags;
+ std::vector<LogRecord> logs;
+};
+
+inline bool operator==(const SpanData& lhs, const SpanData& rhs) {
+ return lhs.span_context == rhs.span_context &&
+ lhs.references == rhs.references &&
+ lhs.operation_name == rhs.operation_name &&
+ lhs.start_timestamp == rhs.start_timestamp &&
+ lhs.duration == rhs.duration && lhs.tags == rhs.tags &&
+ lhs.logs == rhs.logs;
+}
+
+inline bool operator!=(const SpanData& lhs, const SpanData& rhs) {
+ return !(lhs == rhs);
+}
+
+std::ostream& operator<<(std::ostream& out, const SpanData& span_data);
+
+class OPENTRACING_MOCK_TRACER_API Recorder {
+ public:
+ virtual ~Recorder() = default;
+
+ virtual void RecordSpan(SpanData&& span_data) noexcept = 0;
+
+ virtual void Close() noexcept {}
+};
+
+} // namespace mocktracer
+END_OPENTRACING_ABI_NAMESPACE
+} // namespace opentracing
+
+#endif // OPENTRACING_MOCKTRACER_RECORDER_H
diff --git a/mocktracer/include/opentracing/mocktracer/symbols.h b/mocktracer/include/opentracing/mocktracer/symbols.h
new file mode 100644
index 0000000..9c63628
--- /dev/null
+++ b/mocktracer/include/opentracing/mocktracer/symbols.h
@@ -0,0 +1,21 @@
+#ifndef OPENTRACING_MOCK_TRACER_SYMBOLS_H
+#define OPENTRACING_MOCK_TRACER_SYMBOLS_H
+
+#include <opentracing/config.h>
+
+#ifdef _MSC_VER
+// Export if this is our own source, otherwise import:
+#ifndef OPENTRACING_MOCK_TRACER_STATIC
+#ifdef OPENTRACING_MOCK_TRACER_EXPORTS
+#define OPENTRACING_MOCK_TRACER_API __declspec(dllexport)
+#else
+#define OPENTRACING_MOCK_TRACER_API __declspec(dllimport)
+#endif
+#endif
+#endif // _MSC_VER
+
+#ifndef OPENTRACING_MOCK_TRACER_API
+#define OPENTRACING_MOCK_TRACER_API
+#endif
+
+#endif // OPENTRACING_SYMBOLS_H
diff --git a/mocktracer/include/opentracing/mocktracer/tracer.h b/mocktracer/include/opentracing/mocktracer/tracer.h
new file mode 100644
index 0000000..a6d1c5f
--- /dev/null
+++ b/mocktracer/include/opentracing/mocktracer/tracer.h
@@ -0,0 +1,86 @@
+#ifndef OPENTRACING_MOCKTRACER_TRACER_H
+#define OPENTRACING_MOCKTRACER_TRACER_H
+
+#include <opentracing/mocktracer/recorder.h>
+#include <opentracing/mocktracer/symbols.h>
+#include <opentracing/tracer.h>
+#include <map>
+#include <memory>
+#include <mutex>
+
+namespace opentracing {
+BEGIN_OPENTRACING_ABI_NAMESPACE
+namespace mocktracer {
+
+struct PropagationOptions {
+ // Specifies what key to use when injecting and extracting span context.
+ std::string propagation_key = "x-ot-span-context";
+
+ // If inject_error_code is non-zero, MockTracer::Inject fails with
+ // inject_error_code.
+ std::error_code inject_error_code;
+
+ // If extract_error_code is non-zero, MockTracer::Extract fails with
+ // extract_error_code.
+ std::error_code extract_error_code;
+};
+
+struct MockTracerOptions {
+ // Recorder is sent spans when they are finished. If nullptr, all finished
+ // spans are dropped.
+ std::unique_ptr<Recorder> recorder;
+
+ // PropagationOptions allows you to customize how the mocktracer's SpanContext
+ // is propagated.
+ PropagationOptions propagation_options;
+};
+
+// MockTracer provides implements the OpenTracing Tracer API. It provides
+// convenient access to finished spans in such a way as to support testing.
+class OPENTRACING_MOCK_TRACER_API MockTracer
+ : public Tracer,
+ public std::enable_shared_from_this<MockTracer> {
+ public:
+ explicit MockTracer(MockTracerOptions&& options);
+
+ std::unique_ptr<Span> StartSpanWithOptions(
+ string_view operation_name, const StartSpanOptions& options) const
+ noexcept override;
+
+ void Close() noexcept override;
+
+ const std::vector<SpanData>& spans() const noexcept { return spans_; }
+
+ using Tracer::Extract;
+ using Tracer::Inject;
+
+ expected<void> Inject(const SpanContext& sc,
+ std::ostream& writer) const override;
+
+ expected<void> Inject(const SpanContext& sc,
+ const TextMapWriter& writer) const override;
+
+ expected<void> Inject(const SpanContext& sc,
+ const HTTPHeadersWriter& writer) const override;
+
+ expected<std::unique_ptr<SpanContext>> Extract(
+ std::istream& reader) const override;
+
+ expected<std::unique_ptr<SpanContext>> Extract(
+ const TextMapReader& reader) const override;
+
+ expected<std::unique_ptr<SpanContext>> Extract(
+ const HTTPHeadersReader& reader) const override;
+
+ private:
+ std::unique_ptr<Recorder> recorder_;
+ PropagationOptions propagation_options_;
+ std::mutex mutex_;
+ std::vector<SpanData> spans_;
+};
+
+} // namespace mocktracer
+END_OPENTRACING_ABI_NAMESPACE
+} // namespace opentracing
+
+#endif // OPENTRACING_MOCKTRACER_TRACER_H
diff --git a/mocktracer/include/opentracing/mocktracer/tracer_factory.h b/mocktracer/include/opentracing/mocktracer/tracer_factory.h
new file mode 100644
index 0000000..19106ff
--- /dev/null
+++ b/mocktracer/include/opentracing/mocktracer/tracer_factory.h
@@ -0,0 +1,22 @@
+#ifndef OPENTRACING_MOCKTRACER_TRACER_FACTORY_H
+#define OPENTRACING_MOCKTRACER_TRACER_FACTORY_H
+
+#include <opentracing/mocktracer/symbols.h>
+#include <opentracing/tracer_factory.h>
+
+namespace opentracing {
+BEGIN_OPENTRACING_ABI_NAMESPACE
+namespace mocktracer {
+
+class OPENTRACING_MOCK_TRACER_API MockTracerFactory : public TracerFactory {
+ public:
+ expected<std::shared_ptr<Tracer>> MakeTracer(const char* configuration,
+ std::string& error_message) const
+ noexcept override;
+};
+
+} // namespace mocktracer
+END_OPENTRACING_ABI_NAMESPACE
+} // namespace opentracing
+
+#endif // OPENTRACING_MOCKTRACER_TRACER_FACTORY_H
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
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());
+ }
+}