diff --git a/api/include/opentelemetry/context/TBD b/api/include/opentelemetry/context/TBD deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/api/include/opentelemetry/context/context.h b/api/include/opentelemetry/context/context.h new file mode 100644 index 0000000000..1f7caf0ee1 --- /dev/null +++ b/api/include/opentelemetry/context/context.h @@ -0,0 +1,166 @@ +#pragma once + +#include "opentelemetry/context/context_value.h" +#include "opentelemetry/nostd/shared_ptr.h" +#include "opentelemetry/nostd/string_view.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace context +{ + +// The context class provides a context identifier. Is built as a linked list +// of DataList nodes and each context holds a shared_ptr to a place within +// the list that determines which keys and values it has access to. All that +// come before and none that come after. +class Context +{ + +public: + // Creates a context object from a map of keys and identifiers, this will + // hold a shared_ptr to the head of the DataList linked list + template + Context(const T &keys_and_values) + { + head_ = nostd::shared_ptr{new DataList(keys_and_values)}; + } + + // Creates a context object from a key and value, this will + // hold a shared_ptr to the head of the DataList linked list + Context(nostd::string_view key, ContextValue value) + { + head_ = nostd::shared_ptr{new DataList(key, value)}; + } + + // Accepts a new iterable and then returns a new context that + // contains the new key and value data. It attaches the + // exisiting list to the end of the new list. + template + Context SetValues(T &values) noexcept + { + Context context = Context(values); + nostd::shared_ptr &last = context.head_; + while (last->next_ != nullptr) + { + last = last->next_; + } + last->next_ = head_; + return context; + } + + // Accepts a new iterable and then returns a new context that + // contains the new key and value data. It attaches the + // exisiting list to the end of the new list. + Context SetValue(nostd::string_view key, ContextValue value) noexcept + { + Context context = Context(key, value); + context.head_->next_ = head_; + return context; + } + + // Returns the value associated with the passed in key. + context::ContextValue GetValue(const nostd::string_view key) noexcept + { + for (DataList *data = head_.get(); data != nullptr; data = data->next_.get()) + { + if (key.size() == data->key_length_) + { + if (memcmp(key.data(), data->key_, data->key_length_) == 0) + { + return data->value_; + } + } + } + return (int64_t)0; + } + + // Checks for key and returns true if found + bool HasKey(const nostd::string_view key) noexcept + { + for (DataList *data = head_.get(); data != nullptr; data = data->next_.get()) + { + if (key.size() == data->key_length_) + { + if (memcmp(key.data(), data->key_, data->key_length_) == 0) + { + return true; + } + } + } + return false; + } + +private: + Context() = default; + + // A linked list to contain the keys and values of this context node + class DataList + { + public: + nostd::shared_ptr next_; + + char *key_; + + size_t key_length_; + + ContextValue value_; + + DataList() { next_ = nullptr; } + + // Builds a data list off of a key and value iterable and returns the head + template + DataList(const T &keys_and_vals) : key_{nullptr} + { + bool first = true; + auto *node = this; + for (auto &iter : keys_and_vals) + { + if (first) + { + *node = std::move(DataList(iter.first, iter.second)); + first = false; + } + else + { + node->next_ = nostd::shared_ptr(new DataList(iter.first, iter.second)); + node = node->next_.get(); + } + } + } + + // Builds a data list with just a key and value, so it will just be the head + // and returns that head. + DataList(nostd::string_view key, ContextValue value) + { + key_ = new char[key.size()]; + key_length_ = key.size(); + memcpy(key_, key.data(), key.size() * sizeof(char)); + value_ = value; + next_ = nostd::shared_ptr{nullptr}; + } + + DataList &operator=(DataList &&other) + { + key_length_ = other.key_length_; + value_ = std::move(other.value_); + next_ = std::move(other.next_); + + key_ = other.key_; + other.key_ = nullptr; + + return *this; + } + + ~DataList() + { + if (key_ != nullptr) + { + delete[] key_; + } + } + }; + + // Head of the list which holds the keys and values of this context + nostd::shared_ptr head_; +}; +} // namespace context +OPENTELEMETRY_END_NAMESPACE diff --git a/api/include/opentelemetry/context/context_value.h b/api/include/opentelemetry/context/context_value.h new file mode 100644 index 0000000000..8fd2fabe8c --- /dev/null +++ b/api/include/opentelemetry/context/context_value.h @@ -0,0 +1,18 @@ +#pragma once + +#include + +#include "opentelemetry/nostd/shared_ptr.h" +#include "opentelemetry/nostd/span.h" +#include "opentelemetry/nostd/unique_ptr.h" +#include "opentelemetry/nostd/variant.h" +#include "opentelemetry/trace/span_context.h" +#include "opentelemetry/version.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace context +{ +using ContextValue = + nostd::variant>; +} // namespace context +OPENTELEMETRY_END_NAMESPACE diff --git a/api/test/CMakeLists.txt b/api/test/CMakeLists.txt index 8c0833a51e..11cf6d1319 100644 --- a/api/test/CMakeLists.txt +++ b/api/test/CMakeLists.txt @@ -1,4 +1,5 @@ add_subdirectory(core) +add_subdirectory(context) add_subdirectory(plugin) add_subdirectory(nostd) add_subdirectory(trace) diff --git a/api/test/context/BUILD b/api/test/context/BUILD new file mode 100644 index 0000000000..b213710944 --- /dev/null +++ b/api/test/context/BUILD @@ -0,0 +1,12 @@ +load("//bazel:otel_cc_benchmark.bzl", "otel_cc_benchmark") + +cc_test( + name = "context_test", + srcs = [ + "context_test.cc", + ], + deps = [ + "//api", + "@com_google_googletest//:gtest_main", + ], +) diff --git a/api/test/context/CMakeLists.txt b/api/test/context/CMakeLists.txt new file mode 100644 index 0000000000..38d48ef0e8 --- /dev/null +++ b/api/test/context/CMakeLists.txt @@ -0,0 +1,8 @@ +include(GoogleTest) + +foreach(testname context_test) + add_executable(${testname} "${testname}.cc") + target_link_libraries(${testname} ${GTEST_BOTH_LIBRARIES} + ${CMAKE_THREAD_LIBS_INIT} opentelemetry_api) + gtest_add_tests(TARGET ${testname} TEST_PREFIX context. TEST_LIST ${testname}) +endforeach() diff --git a/api/test/context/context_test.cc b/api/test/context/context_test.cc new file mode 100644 index 0000000000..26fc5110bf --- /dev/null +++ b/api/test/context/context_test.cc @@ -0,0 +1,120 @@ +#include "opentelemetry/context/context.h" + +#include + +#include + +using namespace opentelemetry; + +// Tests that the context constructor accepts an std::map. +TEST(ContextTest, ContextIterableAcceptsMap) +{ + std::map map_test = {{"test_key", (int64_t)123}}; + context::Context test_context = context::Context(map_test); +} + +// Tests that the GetValue method returns the expected value. +TEST(ContextTest, ContextGetValueReturnsExpectedValue) +{ + std::map map_test = {{"test_key", (int64_t)123}, + {"foo_key", (int64_t)456}}; + context::Context test_context = context::Context(map_test); + EXPECT_EQ(nostd::get(test_context.GetValue("test_key")), 123); + EXPECT_EQ(nostd::get(test_context.GetValue("foo_key")), 456); +} + +// Tests that the SetValues method accepts an std::map. +TEST(ContextTest, ContextSetValuesAcceptsMap) +{ + std::map map_test = {{"test_key", (int64_t)123}}; + std::map map_test_write = {{"foo_key", (int64_t)456}}; + + context::Context test_context = context::Context(map_test); + context::Context foo_context = test_context.SetValues(map_test_write); + + EXPECT_EQ(nostd::get(foo_context.GetValue("test_key")), 123); + EXPECT_EQ(nostd::get(foo_context.GetValue("foo_key")), 456); +} + +// Tests that the SetValues method accepts a nostd::string_view and +// context::ContextValue. +TEST(ContextTest, ContextSetValuesAcceptsStringViewContextValue) +{ + nostd::string_view string_view_test = "string_view"; + context::ContextValue context_value_test = (int64_t)123; + + context::Context test_context = context::Context(string_view_test, context_value_test); + context::Context foo_context = test_context.SetValue(string_view_test, context_value_test); + + EXPECT_EQ(nostd::get(foo_context.GetValue(string_view_test)), 123); +} + +// Tests that the original context does not change when a value is +// written to it. +TEST(ContextTest, ContextImmutability) +{ + std::map map_test = {{"test_key", (int64_t)123}}; + + context::Context context_test = context::Context(map_test); + context::Context context_foo = context_test.SetValue("foo_key", (int64_t)456); + + EXPECT_NE(nostd::get(context_test.GetValue("foo_key")), 456); +} + +// Tests that writing the same to a context overwrites the original value. +TEST(ContextTest, ContextKeyOverwrite) +{ + std::map map_test = {{"test_key", (int64_t)123}}; + + context::Context context_test = context::Context(map_test); + context::Context context_foo = context_test.SetValue("test_key", (int64_t)456); + + EXPECT_EQ(nostd::get(context_foo.GetValue("test_key")), 456); +} + +// Tests that the new Context Objects inherits the keys and values +// of the original context object. +TEST(ContextTest, ContextInheritance) +{ + using M = std::map; + + M m1 = {{"test_key", (int64_t)123}, {"foo_key", (int64_t)456}}; + M m2 = {{"other_key", (int64_t)789}}; + + context::Context test_context = context::Context(m1); + context::Context foo_context = test_context.SetValues(m2); + + EXPECT_EQ(nostd::get(foo_context.GetValue("test_key")), 123); + EXPECT_EQ(nostd::get(foo_context.GetValue("foo_key")), 456); +} + +// Tests that copying a context copies the key value pairs as expected. +TEST(ContextTest, ContextCopyOperator) +{ + std::map test_map = { + {"test_key", (int64_t)123}, {"foo_key", (int64_t)456}, {"other_key", (int64_t)789}}; + + context::Context test_context = context::Context(test_map); + context::Context copied_context = test_context; + + EXPECT_EQ(nostd::get(copied_context.GetValue("test_key")), 123); + EXPECT_EQ(nostd::get(copied_context.GetValue("foo_key")), 456); + EXPECT_EQ(nostd::get(copied_context.GetValue("other_key")), 789); +} + +// Tests that the Context accepts an empty map. +TEST(ContextTest, ContextEmptyMap) +{ + std::map map_test = {}; + context::Context test_context = context::Context(map_test); +} + +// Tests that if a key exists within a context has key will return true +// false if not. +TEST(ContextTest, ContextHasKey) +{ + std::map map_test = {{"test_key", (int64_t)123}}; + context::Context context_test = context::Context(map_test); + EXPECT_TRUE(context_test.HasKey("test_key")); + EXPECT_FALSE(context_test.HasKey("foo_key")); +}