diff options
Diffstat (limited to '')
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()); + } +} |