From aad78ced11ecd5d4aa1781d0f2785d2bdc75b68e Mon Sep 17 00:00:00 2001 From: Lin Zhihao <59785146+LinZhihao-723@users.noreply.github.com> Date: Thu, 15 Aug 2024 19:55:07 -0400 Subject: [PATCH] ffi: Add class for key-value pair log events. (#507) Co-authored-by: kirkrodrigues <2454684+kirkrodrigues@users.noreply.github.com> --- components/core/CMakeLists.txt | 4 +- .../core/src/clp/ffi/KeyValuePairLogEvent.cpp | 170 +++++++ .../core/src/clp/ffi/KeyValuePairLogEvent.hpp | 81 ++++ components/core/src/clp/ffi/Value.hpp | 11 - components/core/src/clp/ir/EncodedTextAst.hpp | 11 - .../tests/test-ffi_KeyValuePairLogEvent.cpp | 453 ++++++++++++++++++ components/core/tests/test-ffi_Value.cpp | 187 -------- 7 files changed, 707 insertions(+), 210 deletions(-) create mode 100644 components/core/src/clp/ffi/KeyValuePairLogEvent.cpp create mode 100644 components/core/src/clp/ffi/KeyValuePairLogEvent.hpp create mode 100644 components/core/tests/test-ffi_KeyValuePairLogEvent.cpp delete mode 100644 components/core/tests/test-ffi_Value.cpp diff --git a/components/core/CMakeLists.txt b/components/core/CMakeLists.txt index 00c3c7083..8d94a71f8 100644 --- a/components/core/CMakeLists.txt +++ b/components/core/CMakeLists.txt @@ -337,6 +337,8 @@ set(SOURCE_FILES_unitTest src/clp/ffi/ir_stream/Serializer.hpp src/clp/ffi/ir_stream/utils.cpp src/clp/ffi/ir_stream/utils.hpp + src/clp/ffi/KeyValuePairLogEvent.cpp + src/clp/ffi/KeyValuePairLogEvent.hpp src/clp/ffi/SchemaTree.cpp src/clp/ffi/SchemaTree.hpp src/clp/ffi/SchemaTreeNode.hpp @@ -492,8 +494,8 @@ set(SOURCE_FILES_unitTest tests/test-BufferedFileReader.cpp tests/test-EncodedVariableInterpreter.cpp tests/test-encoding_methods.cpp + tests/test-ffi_KeyValuePairLogEvent.cpp tests/test-ffi_SchemaTree.cpp - tests/test-ffi_Value.cpp tests/test-Grep.cpp tests/test-hash_utils.cpp tests/test-ir_encoding_methods.cpp diff --git a/components/core/src/clp/ffi/KeyValuePairLogEvent.cpp b/components/core/src/clp/ffi/KeyValuePairLogEvent.cpp new file mode 100644 index 000000000..d08803e08 --- /dev/null +++ b/components/core/src/clp/ffi/KeyValuePairLogEvent.cpp @@ -0,0 +1,170 @@ +#include "KeyValuePairLogEvent.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "../ir/EncodedTextAst.hpp" +#include "../time_types.hpp" +#include "SchemaTree.hpp" +#include "SchemaTreeNode.hpp" +#include "Value.hpp" + +using clp::ir::EightByteEncodedTextAst; +using clp::ir::FourByteEncodedTextAst; +using std::string; + +namespace clp::ffi { +namespace { +/** + * @param type + * @param value + * @return Whether the given schema tree node type matches the given value's type. + */ +[[nodiscard]] auto +node_type_matches_value_type(SchemaTreeNode::Type type, Value const& value) -> bool; + +/** + * Validates whether the given node-ID value pairs are leaf nodes in the `SchemaTree` forming a + * sub-tree of their own. + * @param schema_tree + * @param node_id_value_pairs + * @return success if the inputs are valid, or an error code indicating the failure: + * - std::errc::operation_not_permitted if a node ID doesn't represent a valid node in the + * schema tree, or a non-leaf node ID is paired with a value. + * - std::errc::protocol_error if the schema tree node type doesn't match the value's type. + * - std::errc::protocol_not_supported if the same key appears more than once under a parent + * node. + */ +[[nodiscard]] auto validate_node_id_value_pairs( + SchemaTree const& schema_tree, + KeyValuePairLogEvent::NodeIdValuePairs const& node_id_value_pairs +) -> std::errc; + +/** + * @param schema_tree + * @param node_id + * @param node_id_value_pairs + * @return Whether the given node is a leaf node in the sub-tree of the `SchemaTree` defined by + * `node_id_value_pairs`. A node is considered a leaf if none of its descendants appear in + * `node_id_value_pairs`. + */ +[[nodiscard]] auto is_leaf_node( + SchemaTree const& schema_tree, + SchemaTreeNode::id_t node_id, + KeyValuePairLogEvent::NodeIdValuePairs const& node_id_value_pairs +) -> bool; + +auto node_type_matches_value_type(SchemaTreeNode::Type type, Value const& value) -> bool { + switch (type) { + case SchemaTreeNode::Type::Obj: + return value.is_null(); + case SchemaTreeNode::Type::Int: + return value.is(); + case SchemaTreeNode::Type::Float: + return value.is(); + case SchemaTreeNode::Type::Bool: + return value.is(); + case SchemaTreeNode::Type::UnstructuredArray: + return value.is() || value.is(); + case SchemaTreeNode::Type::Str: + return value.is() || value.is() + || value.is(); + default: + return false; + } +} + +auto validate_node_id_value_pairs( + SchemaTree const& schema_tree, + KeyValuePairLogEvent::NodeIdValuePairs const& node_id_value_pairs +) -> std::errc { + try { + std::unordered_map> + parent_node_id_to_key_names; + for (auto const& [node_id, value] : node_id_value_pairs) { + if (SchemaTree::cRootId == node_id) { + return std::errc::operation_not_permitted; + } + + auto const& node{schema_tree.get_node(node_id)}; + auto const node_type{node.get_type()}; + if (false == value.has_value()) { + // Value is an empty object (`{}`, which is not the same as `null`) + if (SchemaTreeNode::Type::Obj != node_type) { + return std::errc::protocol_error; + } + } else if (false == node_type_matches_value_type(node_type, value.value())) { + return std::errc::protocol_error; + } + + if (SchemaTreeNode::Type::Obj == node_type + && false == is_leaf_node(schema_tree, node_id, node_id_value_pairs)) + { + // The node's value is `null` or `{}` but its descendants appear in + // `node_id_value_pairs`. + return std::errc::operation_not_permitted; + } + + auto const parent_node_id{node.get_parent_id()}; + auto const key_name{node.get_key_name()}; + if (parent_node_id_to_key_names.contains(parent_node_id)) { + auto const [it, new_key_inserted]{ + parent_node_id_to_key_names.at(parent_node_id).emplace(key_name) + }; + if (false == new_key_inserted) { + // The key is duplicated under the same parent + return std::errc::protocol_not_supported; + } + } else { + parent_node_id_to_key_names.emplace(parent_node_id, std::unordered_set{key_name}); + } + } + } catch (SchemaTree::OperationFailed const& ex) { + return std::errc::operation_not_permitted; + } + return std::errc{}; +} + +auto is_leaf_node( + SchemaTree const& schema_tree, + SchemaTreeNode::id_t node_id, + KeyValuePairLogEvent::NodeIdValuePairs const& node_id_value_pairs +) -> bool { + std::vector dfs_stack; + dfs_stack.reserve(schema_tree.get_size()); + dfs_stack.push_back(node_id); + while (false == dfs_stack.empty()) { + auto const curr_node_id{dfs_stack.back()}; + dfs_stack.pop_back(); + for (auto const child_node_id : schema_tree.get_node(curr_node_id).get_children_ids()) { + if (node_id_value_pairs.contains(child_node_id)) { + return false; + } + dfs_stack.push_back(child_node_id); + } + } + return true; +} +} // namespace + +auto KeyValuePairLogEvent::create( + std::shared_ptr schema_tree, + NodeIdValuePairs node_id_value_pairs, + UtcOffset utc_offset +) -> OUTCOME_V2_NAMESPACE::std_result { + if (auto const ret_val{validate_node_id_value_pairs(*schema_tree, node_id_value_pairs)}; + std::errc{} != ret_val) + { + return ret_val; + } + return KeyValuePairLogEvent{std::move(schema_tree), std::move(node_id_value_pairs), utc_offset}; +} +} // namespace clp::ffi diff --git a/components/core/src/clp/ffi/KeyValuePairLogEvent.hpp b/components/core/src/clp/ffi/KeyValuePairLogEvent.hpp new file mode 100644 index 000000000..ad50ee41f --- /dev/null +++ b/components/core/src/clp/ffi/KeyValuePairLogEvent.hpp @@ -0,0 +1,81 @@ +#ifndef CLP_FFI_KEYVALUEPAIRLOGEVENT_HPP +#define CLP_FFI_KEYVALUEPAIRLOGEVENT_HPP + +#include +#include +#include +#include + +#include + +#include "../time_types.hpp" +#include "SchemaTree.hpp" +#include "SchemaTreeNode.hpp" +#include "Value.hpp" + +namespace clp::ffi { +/** + * A log event containing key-value pairs. Each event contains: + * - A collection of node-ID & value pairs, where each pair represents a leaf `SchemaTreeNode` in + * the `SchemaTree`. + * - A reference to the `SchemaTree` + * - The UTC offset of the current log event + */ +class KeyValuePairLogEvent { +public: + // Types + using NodeIdValuePairs = std::unordered_map>; + + // Factory functions + /** + * @param schema_tree + * @param node_id_value_pairs + * @param utc_offset + * @return A result containing the key-value pair log event or an error code indicating the + * failure. See `valdiate_node_id_value_pairs` for the possible error codes. + */ + [[nodiscard]] static auto create( + std::shared_ptr schema_tree, + NodeIdValuePairs node_id_value_pairs, + UtcOffset utc_offset + ) -> OUTCOME_V2_NAMESPACE::std_result; + + // Disable copy constructor and assignment operator + KeyValuePairLogEvent(KeyValuePairLogEvent const&) = delete; + auto operator=(KeyValuePairLogEvent const&) -> KeyValuePairLogEvent& = delete; + + // Default move constructor and assignment operator + KeyValuePairLogEvent(KeyValuePairLogEvent&&) = default; + auto operator=(KeyValuePairLogEvent&&) -> KeyValuePairLogEvent& = default; + + // Destructor + ~KeyValuePairLogEvent() = default; + + // Methods + [[nodiscard]] auto get_schema_tree() const -> SchemaTree const& { return *m_schema_tree; } + + [[nodiscard]] auto get_node_id_value_pairs() const -> NodeIdValuePairs const& { + return m_node_id_value_pairs; + } + + [[nodiscard]] auto get_utc_offset() const -> UtcOffset { return m_utc_offset; } + +private: + // Constructor + KeyValuePairLogEvent( + std::shared_ptr schema_tree, + NodeIdValuePairs node_id_value_pairs, + UtcOffset utc_offset + ) + : m_schema_tree{std::move(schema_tree)}, + m_node_id_value_pairs{std::move(node_id_value_pairs)}, + m_utc_offset{utc_offset} {} + + // Variables + std::shared_ptr m_schema_tree; + NodeIdValuePairs m_node_id_value_pairs; + UtcOffset m_utc_offset{0}; +}; +} // namespace clp::ffi + +#endif // CLP_FFI_KEYVALUEPAIRLOGEVENT_HPP diff --git a/components/core/src/clp/ffi/Value.hpp b/components/core/src/clp/ffi/Value.hpp index db719a1fb..961d12366 100644 --- a/components/core/src/clp/ffi/Value.hpp +++ b/components/core/src/clp/ffi/Value.hpp @@ -150,17 +150,6 @@ class Value { template explicit Value(T value) : m_value{value} {} - // Disable copy constructor and assignment operator - Value(Value const&) = delete; - auto operator=(Value const&) -> Value& = delete; - - // Default move constructor and assignment operator - Value(Value&&) = default; - auto operator=(Value&&) -> Value& = default; - - // Destructor - ~Value() = default; - // Methods /** * @tparam T diff --git a/components/core/src/clp/ir/EncodedTextAst.hpp b/components/core/src/clp/ir/EncodedTextAst.hpp index 48adb100a..e18759780 100644 --- a/components/core/src/clp/ir/EncodedTextAst.hpp +++ b/components/core/src/clp/ir/EncodedTextAst.hpp @@ -26,17 +26,6 @@ class EncodedTextAst { m_dict_vars{std::move(dict_vars)}, m_encoded_vars{std::move(encoded_vars)} {} - // Disable copy constructor and assignment operator - EncodedTextAst(EncodedTextAst const&) = delete; - auto operator=(EncodedTextAst const&) -> EncodedTextAst& = delete; - - // Default move constructor and assignment operator - EncodedTextAst(EncodedTextAst&&) = default; - auto operator=(EncodedTextAst&&) -> EncodedTextAst& = default; - - // Destructor - ~EncodedTextAst() = default; - // Methods auto operator==(EncodedTextAst const&) const -> bool = default; diff --git a/components/core/tests/test-ffi_KeyValuePairLogEvent.cpp b/components/core/tests/test-ffi_KeyValuePairLogEvent.cpp new file mode 100644 index 000000000..4820a5af1 --- /dev/null +++ b/components/core/tests/test-ffi_KeyValuePairLogEvent.cpp @@ -0,0 +1,453 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "../src/clp/ffi/encoding_methods.hpp" +#include "../src/clp/ffi/KeyValuePairLogEvent.hpp" +#include "../src/clp/ffi/SchemaTree.hpp" +#include "../src/clp/ffi/SchemaTreeNode.hpp" +#include "../src/clp/ffi/Value.hpp" +#include "../src/clp/ir/EncodedTextAst.hpp" +#include "../src/clp/ir/types.hpp" +#include "../src/clp/time_types.hpp" + +using clp::ffi::KeyValuePairLogEvent; +using clp::ffi::SchemaTree; +using clp::ffi::SchemaTreeNode; +using clp::ffi::Value; +using clp::ffi::value_bool_t; +using clp::ffi::value_float_t; +using clp::ffi::value_int_t; +using clp::ir::eight_byte_encoded_variable_t; +using clp::ir::EightByteEncodedTextAst; +using clp::ir::four_byte_encoded_variable_t; +using clp::ir::FourByteEncodedTextAst; +using clp::UtcOffset; +using std::string; +using std::vector; + +namespace { +constexpr std::string_view cStringToEncode{"uid=0, CPU usage: 99.99%, \"user_name\"=YScope"}; + +/** + * Parses and encodes the given string as an instance of `EncodedTextAst`. + * @tparam encoded_variable_t + * @param text + * @return The encoded result. + */ +template +requires(std::is_same_v + || std::is_same_v) +[[nodiscard]] auto get_encoded_text_ast(std::string_view text +) -> clp::ir::EncodedTextAst; + +/** + * Tests that `Value::is` returns true for the given type and false for all others. + * @tparam Type The type to query. + * @param value The value to test against. + */ +template +auto test_value_is(Value const& value) -> void; + +/** + * Tests `Value::get_immutable_view` either: + * 1. returns the expected value with the expected type for the given type and value; + * 2. throws for any other type. + * @tparam Type The type to query. + * @param value The value to test against. + * @param typed_value The typed value to compare with. + */ +template +auto test_value_get_immutable_view(Value const& value, Type const& typed_value) -> void; + +/** + * Generates invalid node-ID value pairs with values that don't match the type of the schema tree + * node with the given ID. + * @param schema_tree + * @param node_id + * @param invalid_node_id_value_pairs Returns the pairs after insertion. + */ +auto insert_invalid_node_id_value_pairs_with_node_type_errors( + SchemaTree const& schema_tree, + SchemaTreeNode::id_t node_id, + KeyValuePairLogEvent::NodeIdValuePairs& invalid_node_id_value_pairs +) -> void; + +template +requires(std::is_same_v + || std::is_same_v) +auto get_encoded_text_ast(std::string_view text) -> clp::ir::EncodedTextAst { + string logtype; + vector encoded_vars; + vector dict_var_bounds; + REQUIRE(clp::ffi::encode_message(text, logtype, encoded_vars, dict_var_bounds)); + REQUIRE(((dict_var_bounds.size() % 2) == 0)); + + vector dict_vars; + for (size_t i{0}; i < dict_var_bounds.size(); i += 2) { + auto const begin_pos{static_cast(dict_var_bounds[i])}; + auto const end_pos{static_cast(dict_var_bounds[i + 1])}; + dict_vars.emplace_back(text.cbegin() + begin_pos, text.cbegin() + end_pos); + } + + return clp::ir::EncodedTextAst{logtype, dict_vars, encoded_vars}; +} + +template +// NOLINTNEXTLINE(readability-function-cognitive-complexity) +auto test_value_is(Value const& value) -> void { + REQUIRE((std::is_same_v == value.is_null())); + REQUIRE((std::is_same_v == value.is())); + REQUIRE((std::is_same_v == value.is())); + REQUIRE((std::is_same_v == value.is())); + REQUIRE((std::is_same_v == value.is())); + REQUIRE((std::is_same_v == value.is())); + REQUIRE((std::is_same_v == value.is())); +} + +template +// NOLINTNEXTLINE(readability-function-cognitive-complexity) +auto test_value_get_immutable_view(Value const& value, Type const& typed_value) -> void { + if constexpr (std::is_same_v) { + REQUIRE((value.get_immutable_view() == typed_value)); + REQUIRE((std::is_same_v())>)); + } else { + REQUIRE_THROWS(value.get_immutable_view()); + } + + if constexpr (std::is_same_v) { + REQUIRE((value.get_immutable_view() == typed_value)); + REQUIRE((std::is_same_v())>)); + } else { + REQUIRE_THROWS(value.get_immutable_view()); + } + + if constexpr (std::is_same_v) { + REQUIRE((value.get_immutable_view() == typed_value)); + REQUIRE((std::is_same_v())>)); + } else { + REQUIRE_THROWS(value.get_immutable_view()); + } + + if constexpr (std::is_same_v) { + REQUIRE((value.get_immutable_view() == typed_value)); + REQUIRE((std::is_same_v())>)); + } else { + REQUIRE_THROWS(value.get_immutable_view()); + } + + if constexpr (std::is_same_v) { + REQUIRE((value.get_immutable_view() == typed_value)); + REQUIRE((std::is_same_v< + EightByteEncodedTextAst const&, + decltype(value.get_immutable_view())>)); + } else { + REQUIRE_THROWS(value.get_immutable_view()); + } + + if constexpr (std::is_same_v) { + REQUIRE((value.get_immutable_view() == typed_value)); + REQUIRE((std::is_same_v< + FourByteEncodedTextAst const&, + decltype(value.get_immutable_view())>)); + } else { + REQUIRE_THROWS(value.get_immutable_view()); + } +} + +auto insert_invalid_node_id_value_pairs_with_node_type_errors( + SchemaTree const& schema_tree, + SchemaTreeNode::id_t node_id, + KeyValuePairLogEvent::NodeIdValuePairs& invalid_node_id_value_pairs +) -> void { + REQUIRE((node_id < schema_tree.get_size())); + auto const node_type{schema_tree.get_node(node_id).get_type()}; + if (SchemaTreeNode::Type::Int != node_type) { + invalid_node_id_value_pairs.emplace(node_id, Value{static_cast(0)}); + } + if (SchemaTreeNode::Type::Float != node_type) { + invalid_node_id_value_pairs.emplace(node_id, Value{static_cast(0.0)}); + } + if (SchemaTreeNode::Type::Bool != node_type) { + invalid_node_id_value_pairs.emplace(node_id, Value{static_cast(false)}); + } + if (SchemaTreeNode::Type::Str != node_type) { + invalid_node_id_value_pairs.emplace(node_id, Value{static_cast("Test")}); + if (SchemaTreeNode::Type::UnstructuredArray != node_type) { + invalid_node_id_value_pairs.emplace( + node_id, + Value{get_encoded_text_ast(cStringToEncode)} + ); + invalid_node_id_value_pairs.emplace( + node_id, + Value{get_encoded_text_ast(cStringToEncode)} + ); + } + } + if (SchemaTreeNode::Type::Obj != node_type) { + invalid_node_id_value_pairs.emplace(node_id, std::nullopt); + invalid_node_id_value_pairs.emplace(node_id, Value{}); + } +} +} // namespace + +TEST_CASE("ffi_Value_basic", "[ffi][Value]") { + Value const null_value; + test_value_is(null_value); + test_value_get_immutable_view(null_value, std::monostate{}); + + constexpr value_int_t cIntVal{1000}; + Value const int_value{cIntVal}; + test_value_is(int_value); + test_value_get_immutable_view(int_value, cIntVal); + + constexpr value_float_t cFloatValue{1000.0001}; + Value const float_value{cFloatValue}; + test_value_is(float_value); + test_value_get_immutable_view(float_value, cFloatValue); + + constexpr value_bool_t cBoolVal{false}; + Value const bool_value{cBoolVal}; + test_value_is(bool_value); + test_value_get_immutable_view(bool_value, cBoolVal); + + constexpr std::string_view cStringVal{"This is a test string message"}; + Value const string_value{string{cStringVal}}; + test_value_is(string_value); + test_value_get_immutable_view(string_value, string{cStringVal}); + + Value const eight_byte_encoded_text_ast_value{ + get_encoded_text_ast(cStringToEncode) + }; + test_value_is(eight_byte_encoded_text_ast_value); + test_value_get_immutable_view( + eight_byte_encoded_text_ast_value, + get_encoded_text_ast(cStringToEncode) + ); + + Value const four_byte_encoded_text_ast_value{ + get_encoded_text_ast(cStringToEncode) + }; + test_value_is(four_byte_encoded_text_ast_value); + test_value_get_immutable_view( + four_byte_encoded_text_ast_value, + get_encoded_text_ast(cStringToEncode) + ); +} + +// NOLINTNEXTLINE(readability-function-cognitive-complexity) +TEST_CASE("ffi_KeyValuePairLogEvent_create", "[ffi]") { + /* + * <0:root:Obj> + * | + * |------------> <1:a:Obj> + * | | + * |--> <2:a:Int> |--> <3:b:Obj> + * | + * |------------> <4:c:Obj> + * | | + * |--> <5:d:Str> |--> <7:a:UnstructuredArray> + * | | + * |--> <6:d:Bool> |--> <8:d:Str> + * | | + * |--> <10:e:Obj> |--> <9:d:Float> + * | + * |--> <11:f:Obj> + */ + auto const schema_tree{std::make_shared()}; + std::vector const locators{ + {SchemaTree::cRootId, "a", SchemaTreeNode::Type::Obj}, + {SchemaTree::cRootId, "a", SchemaTreeNode::Type::Int}, + {1, "b", SchemaTreeNode::Type::Obj}, + {3, "c", SchemaTreeNode::Type::Obj}, + {3, "d", SchemaTreeNode::Type::Str}, + {3, "d", SchemaTreeNode::Type::Bool}, + {4, "a", SchemaTreeNode::Type::UnstructuredArray}, + {4, "d", SchemaTreeNode::Type::Str}, + {4, "d", SchemaTreeNode::Type::Float}, + {3, "e", SchemaTreeNode::Type::Obj}, + {4, "f", SchemaTreeNode::Type::Obj} + }; + for (auto const& locator : locators) { + REQUIRE_NOTHROW(schema_tree->insert_node(locator)); + } + + SECTION("Test empty ID-value pairs") { + KeyValuePairLogEvent::NodeIdValuePairs node_id_value_pairs; + auto const result{KeyValuePairLogEvent::create( + schema_tree, + std::move(node_id_value_pairs), + UtcOffset{0} + )}; + REQUIRE_FALSE(result.has_error()); + } + + SECTION("Test mismatched types") { + KeyValuePairLogEvent::NodeIdValuePairs invalid_node_id_value_pairs; + // NOLINTBEGIN(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers) + // Int: + insert_invalid_node_id_value_pairs_with_node_type_errors( + *schema_tree, + 2, + invalid_node_id_value_pairs + ); + + // Float: + insert_invalid_node_id_value_pairs_with_node_type_errors( + *schema_tree, + 9, + invalid_node_id_value_pairs + ); + + // Bool: + insert_invalid_node_id_value_pairs_with_node_type_errors( + *schema_tree, + 6, + invalid_node_id_value_pairs + ); + + // Str: + insert_invalid_node_id_value_pairs_with_node_type_errors( + *schema_tree, + 5, + invalid_node_id_value_pairs + ); + + // UnstructuredArray: + insert_invalid_node_id_value_pairs_with_node_type_errors( + *schema_tree, + 7, + invalid_node_id_value_pairs + ); + + // Obj: + insert_invalid_node_id_value_pairs_with_node_type_errors( + *schema_tree, + 3, + invalid_node_id_value_pairs + ); + // NOLINTEND(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers) + + for (auto const& [node_id, optional_value] : invalid_node_id_value_pairs) { + KeyValuePairLogEvent::NodeIdValuePairs node_id_value_pair_to_test; + if (optional_value.has_value()) { + node_id_value_pair_to_test.emplace(node_id, optional_value.value()); + } else { + node_id_value_pair_to_test.emplace(node_id, std::nullopt); + } + auto const result{KeyValuePairLogEvent::create( + schema_tree, + std::move(node_id_value_pair_to_test), + UtcOffset{0} + )}; + REQUIRE(result.has_error()); + auto const& err{result.error()}; + REQUIRE((std::errc::protocol_error == err)); + } + } + + SECTION("Test valid ID-value pairs") { + KeyValuePairLogEvent::NodeIdValuePairs node_id_value_pairs; + /* + * The sub schema tree of `node_id_value_pairs`: + * <0:root:Obj> + * | + * |------------> <1:a:Obj> + * | | + * |--> <2:a:Int> |--> <3:b:Obj> + * | + * |------------> <4:c:Obj> + * | | + * |--> <5:d:Str> |--> <7:a:UnstructuredArray> + * | | + * | |--> <8:d:Str> + * | | + * |--> <10:e:Obj> | + * | + * |--> <11:f:Obj> + */ + // NOLINTBEGIN(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers) + node_id_value_pairs.emplace(2, Value{static_cast(0)}); + node_id_value_pairs.emplace(5, Value{string{"Test"}}); + node_id_value_pairs.emplace( + 8, + Value{get_encoded_text_ast(cStringToEncode)} + ); + node_id_value_pairs.emplace( + 7, + Value{get_encoded_text_ast(cStringToEncode)} + ); + node_id_value_pairs.emplace(10, Value{}); + node_id_value_pairs.emplace(11, std::nullopt); + // NOLINTEND(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers) + auto const result{ + KeyValuePairLogEvent::create(schema_tree, node_id_value_pairs, UtcOffset{0}) + }; + REQUIRE_FALSE(result.has_error()); + + SECTION("Test duplicated key conflict on node #3") { + // NOLINTNEXTLINE(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers) + node_id_value_pairs.emplace(6, Value{static_cast(false)}); + auto const result{ + KeyValuePairLogEvent::create(schema_tree, node_id_value_pairs, UtcOffset{0}) + }; + REQUIRE(result.has_error()); + REQUIRE((std::errc::protocol_not_supported == result.error())); + } + + SECTION("Test duplicated key conflict on node #4") { + // NOLINTNEXTLINE(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers) + node_id_value_pairs.emplace(9, Value{static_cast(0.0)}); + auto const result{ + KeyValuePairLogEvent::create(schema_tree, node_id_value_pairs, UtcOffset{0}) + }; + REQUIRE(result.has_error()); + REQUIRE((std::errc::protocol_not_supported == result.error())); + } + + SECTION("Test invalid sub-tree on node #3") { + node_id_value_pairs.emplace(3, std::nullopt); + auto const result{ + KeyValuePairLogEvent::create(schema_tree, node_id_value_pairs, UtcOffset{0}) + }; + // Node #3 is empty, but its descendants appear in the sub schema tree (node #5 & #10) + REQUIRE(result.has_error()); + REQUIRE((std::errc::operation_not_permitted == result.error())); + } + + SECTION("Test invalid sub-tree on node #4") { + node_id_value_pairs.emplace(4, Value{}); + auto const result{ + KeyValuePairLogEvent::create(schema_tree, node_id_value_pairs, UtcOffset{0}) + }; + // Node #4 is null, but its descendants appear in the sub schema tree (node #5 & #10) + REQUIRE(result.has_error()); + REQUIRE((std::errc::operation_not_permitted == result.error())); + } + } + + SECTION("Test out-of-bound node ID") { + KeyValuePairLogEvent::NodeIdValuePairs node_id_value_pairs_out_of_bound; + node_id_value_pairs_out_of_bound.emplace( + static_cast(schema_tree->get_size()), + Value{} + ); + auto const out_of_bound_result{KeyValuePairLogEvent::create( + schema_tree, + std::move(node_id_value_pairs_out_of_bound), + UtcOffset{0} + )}; + REQUIRE(out_of_bound_result.has_error()); + REQUIRE((std::errc::operation_not_permitted == out_of_bound_result.error())); + } +} diff --git a/components/core/tests/test-ffi_Value.cpp b/components/core/tests/test-ffi_Value.cpp deleted file mode 100644 index 33764c4ff..000000000 --- a/components/core/tests/test-ffi_Value.cpp +++ /dev/null @@ -1,187 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include - -#include - -#include "../src/clp/ffi/encoding_methods.hpp" -#include "../src/clp/ffi/Value.hpp" -#include "../src/clp/ir/EncodedTextAst.hpp" -#include "../src/clp/ir/types.hpp" - -using clp::ffi::Value; -using clp::ffi::value_bool_t; -using clp::ffi::value_float_t; -using clp::ffi::value_int_t; -using clp::ir::eight_byte_encoded_variable_t; -using clp::ir::EightByteEncodedTextAst; -using clp::ir::four_byte_encoded_variable_t; -using clp::ir::FourByteEncodedTextAst; -using std::string; -using std::vector; - -namespace { -/** - * Parses and encodes the given string as an instance of `EncodedTextAst`. - * @tparam encoded_variable_t - * @param text - * @return The encoded result. - */ -template -requires(std::is_same_v - || std::is_same_v) -[[nodiscard]] auto get_encoded_text_ast(std::string_view text -) -> clp::ir::EncodedTextAst; - -/** - * Tests that `Value::is` returns true for the given type and false for all others. - * @tparam Type The type to query. - * @param value The value to test against. - */ -template -auto test_value_is(Value const& value) -> void; - -/** - * Tests `Value::get_immutable_view` either: - * 1. returns the expected value with the expected type for the given type and value; - * 2. throws for any other type. - * @tparam Type The type to query. - * @param value The value to test against. - * @param typed_value The typed value to compare with. - */ -template -auto test_value_get_immutable_view(Value const& value, Type const& typed_value) -> void; - -// Implementation - -template -requires(std::is_same_v - || std::is_same_v) -auto get_encoded_text_ast(std::string_view text) -> clp::ir::EncodedTextAst { - string logtype; - vector encoded_vars; - vector dict_var_bounds; - REQUIRE(clp::ffi::encode_message(text, logtype, encoded_vars, dict_var_bounds)); - REQUIRE(((dict_var_bounds.size() % 2) == 0)); - - vector dict_vars; - for (size_t i{0}; i < dict_var_bounds.size(); i += 2) { - auto const begin_pos{static_cast(dict_var_bounds[i])}; - auto const end_pos{static_cast(dict_var_bounds[i + 1])}; - dict_vars.emplace_back(text.cbegin() + begin_pos, text.cbegin() + end_pos); - } - - return clp::ir::EncodedTextAst{logtype, dict_vars, encoded_vars}; -} - -template -// NOLINTNEXTLINE(readability-function-cognitive-complexity) -auto test_value_is(Value const& value) -> void { - REQUIRE((std::is_same_v == value.is_null())); - REQUIRE((std::is_same_v == value.is())); - REQUIRE((std::is_same_v == value.is())); - REQUIRE((std::is_same_v == value.is())); - REQUIRE((std::is_same_v == value.is())); - REQUIRE((std::is_same_v == value.is())); - REQUIRE((std::is_same_v == value.is())); -} - -template -// NOLINTNEXTLINE(readability-function-cognitive-complexity) -auto test_value_get_immutable_view(Value const& value, Type const& typed_value) -> void { - if constexpr (std::is_same_v) { - REQUIRE((value.get_immutable_view() == typed_value)); - REQUIRE((std::is_same_v())>)); - } else { - REQUIRE_THROWS(value.get_immutable_view()); - } - - if constexpr (std::is_same_v) { - REQUIRE((value.get_immutable_view() == typed_value)); - REQUIRE((std::is_same_v())>)); - } else { - REQUIRE_THROWS(value.get_immutable_view()); - } - - if constexpr (std::is_same_v) { - REQUIRE((value.get_immutable_view() == typed_value)); - REQUIRE((std::is_same_v())>)); - } else { - REQUIRE_THROWS(value.get_immutable_view()); - } - - if constexpr (std::is_same_v) { - REQUIRE((value.get_immutable_view() == typed_value)); - REQUIRE((std::is_same_v())>)); - } else { - REQUIRE_THROWS(value.get_immutable_view()); - } - - if constexpr (std::is_same_v) { - REQUIRE((value.get_immutable_view() == typed_value)); - REQUIRE((std::is_same_v< - EightByteEncodedTextAst const&, - decltype(value.get_immutable_view())>)); - } else { - REQUIRE_THROWS(value.get_immutable_view()); - } - - if constexpr (std::is_same_v) { - REQUIRE((value.get_immutable_view() == typed_value)); - REQUIRE((std::is_same_v< - FourByteEncodedTextAst const&, - decltype(value.get_immutable_view())>)); - } else { - REQUIRE_THROWS(value.get_immutable_view()); - } -} -} // namespace - -TEST_CASE("ffi_Value_basic", "[ffi][Value]") { - Value const null_value; - test_value_is(null_value); - test_value_get_immutable_view(null_value, std::monostate{}); - - constexpr value_int_t cIntVal{1000}; - Value const int_value{cIntVal}; - test_value_is(int_value); - test_value_get_immutable_view(int_value, cIntVal); - - constexpr value_float_t cFloatValue{1000.0001}; - Value const float_value{cFloatValue}; - test_value_is(float_value); - test_value_get_immutable_view(float_value, cFloatValue); - - constexpr value_bool_t cBoolVal{false}; - Value const bool_value{cBoolVal}; - test_value_is(bool_value); - test_value_get_immutable_view(bool_value, cBoolVal); - - constexpr std::string_view cStringVal{"This is a test string message"}; - Value const string_value{string{cStringVal}}; - test_value_is(string_value); - test_value_get_immutable_view(string_value, string{cStringVal}); - - constexpr std::string_view cStringToEncode{"uid=0, CPU usage: 99.99%, \"user_name\"=YScope"}; - Value const eight_byte_encoded_text_ast_value{ - get_encoded_text_ast(cStringToEncode) - }; - test_value_is(eight_byte_encoded_text_ast_value); - test_value_get_immutable_view( - eight_byte_encoded_text_ast_value, - get_encoded_text_ast(cStringToEncode) - ); - - Value const four_byte_encoded_text_ast_value{ - get_encoded_text_ast(cStringToEncode) - }; - test_value_is(four_byte_encoded_text_ast_value); - test_value_get_immutable_view( - four_byte_encoded_text_ast_value, - get_encoded_text_ast(cStringToEncode) - ); -}