diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 3769591..bf5f8d0 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -19,12 +19,8 @@ jobs: cxx-compiler: clang++ steps: - uses: actions/checkout@v3 - # language=bash - - run: | - # TODO https://github.com/OpenCyphal/docker_toolchains/issues/36 - dpkg --add-architecture i386 - apt-get update - apt-get install -y libgtest-dev libgtest-dev:i386 + with: + submodules: true # language=bash - run: > cmake @@ -65,12 +61,8 @@ jobs: cxx-compiler: clang++ steps: - uses: actions/checkout@v3 - # language=bash - - run: | - # TODO https://github.com/OpenCyphal/docker_toolchains/issues/36 - dpkg --add-architecture i386 - apt-get update - apt-get install -y libgtest-dev libgtest-dev:i386 + with: + submodules: true # language=bash - run: > cmake @@ -147,6 +139,7 @@ jobs: - uses: actions/checkout@v3 with: fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis + submodules: true - uses: actions/setup-java@v3 with: java-version: 11 @@ -154,10 +147,6 @@ jobs: # language=bash - run: | clang --version - # TODO https://github.com/OpenCyphal/docker_toolchains/issues/36 - dpkg --add-architecture i386 - apt-get update - apt-get install -y libgtest-dev libgtest-dev:i386 - name: Install Sonar tools env: SONAR_SCANNER_DOWNLOAD_URL: https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-${{ env.SONAR_SCANNER_VERSION }}-linux.zip diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..82775a4 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "submodules/unity"] + path = submodules/unity + url = https://github.com/ThrowTheSwitch/Unity diff --git a/libudpard/udpard.c b/libudpard/udpard.c index 2122c53..cbf162d 100644 --- a/libudpard/udpard.c +++ b/libudpard/udpard.c @@ -24,11 +24,6 @@ # define UDPARD_ASSERT(x) assert(x) // NOSONAR #endif -/// This macro is needed for testing and for library development. -#ifndef UDPARD_PRIVATE -# define UDPARD_PRIVATE static inline -#endif - #if !defined(__STDC_VERSION__) || (__STDC_VERSION__ < 199901L) # error "Unsupported language: ISO C99 or a newer version is required." #endif @@ -65,38 +60,38 @@ typedef struct #define SUBJECT_MULTICAST_GROUP_ADDRESS_MASK 0xEF000000UL #define SERVICE_MULTICAST_GROUP_ADDRESS_MASK 0xEF010000UL -UDPARD_PRIVATE uint32_t makeSubjectIPGroupAddress(const UdpardPortID subject_id) +static inline uint32_t makeSubjectIPGroupAddress(const UdpardPortID subject_id) { return SUBJECT_MULTICAST_GROUP_ADDRESS_MASK | ((uint32_t) subject_id); } -UDPARD_PRIVATE uint32_t makeServiceIPGroupAddress(const UdpardNodeID destination_node_id) +static inline uint32_t makeServiceIPGroupAddress(const UdpardNodeID destination_node_id) { return SERVICE_MULTICAST_GROUP_ADDRESS_MASK | ((uint32_t) destination_node_id); } -UDPARD_PRIVATE UdpardUDPIPEndpoint makeSubjectUDPIPEndpoint(const UdpardPortID subject_id) +static inline UdpardUDPIPEndpoint makeSubjectUDPIPEndpoint(const UdpardPortID subject_id) { return (UdpardUDPIPEndpoint){.ip_address = makeSubjectIPGroupAddress(subject_id), .udp_port = UDP_PORT}; } -UDPARD_PRIVATE UdpardUDPIPEndpoint makeServiceUDPIPEndpoint(const UdpardNodeID destination_node_id) +static inline UdpardUDPIPEndpoint makeServiceUDPIPEndpoint(const UdpardNodeID destination_node_id) { return (UdpardUDPIPEndpoint){.ip_address = makeServiceIPGroupAddress(destination_node_id), .udp_port = UDP_PORT}; } /// Used for inserting new items into AVL trees. Refer to the documentation for cavlSearch() for details. -UDPARD_PRIVATE UdpardTreeNode* avlTrivialFactory(void* const user_reference) +static inline UdpardTreeNode* avlTrivialFactory(void* const user_reference) { return (UdpardTreeNode*) user_reference; } -UDPARD_PRIVATE size_t smaller(const size_t a, const size_t b) +static inline size_t smaller(const size_t a, const size_t b) { return (a < b) ? a : b; } -UDPARD_PRIVATE size_t larger(const size_t a, const size_t b) +static inline size_t larger(const size_t a, const size_t b) { return (a > b) ? a : b; } @@ -107,7 +102,7 @@ UDPARD_PRIVATE size_t larger(const size_t a, const size_t b) #define HEADER_CRC_RESIDUE 0x0000U #define HEADER_CRC_SIZE_BYTES 2U -UDPARD_PRIVATE uint16_t headerCRCAddByte(const uint16_t crc, const byte_t byte) +static inline uint16_t headerCRCAddByte(const uint16_t crc, const byte_t byte) { static const uint16_t Table[256] = { 0x0000U, 0x1021U, 0x2042U, 0x3063U, 0x4084U, 0x50A5U, 0x60C6U, 0x70E7U, 0x8108U, 0x9129U, 0xA14AU, 0xB16BU, @@ -137,7 +132,7 @@ UDPARD_PRIVATE uint16_t headerCRCAddByte(const uint16_t crc, const byte_t byte) Table[(uint16_t) ((uint16_t) (crc >> ByteWidth) ^ byte) & ByteMask]); } -UDPARD_PRIVATE uint16_t headerCRCCompute(const size_t size, const void* const data) +static inline uint16_t headerCRCCompute(const size_t size, const void* const data) { UDPARD_ASSERT((data != NULL) || (size == 0U)); uint16_t out = HEADER_CRC_INITIAL; @@ -157,7 +152,7 @@ UDPARD_PRIVATE uint16_t headerCRCCompute(const size_t size, const void* const da #define TRANSFER_CRC_RESIDUE_BEFORE_OUTPUT_XOR 0xB798B438UL #define TRANSFER_CRC_SIZE_BYTES 4U -UDPARD_PRIVATE uint32_t transferCRCAddByte(const uint32_t crc, const byte_t byte) +static inline uint32_t transferCRCAddByte(const uint32_t crc, const byte_t byte) { static const uint32_t Table[256] = { 0x00000000UL, 0xF26B8303UL, 0xE13B70F7UL, 0x1350F3F4UL, 0xC79A971FUL, 0x35F1141CUL, 0x26A1E7E8UL, 0xD4CA64EBUL, @@ -197,7 +192,7 @@ UDPARD_PRIVATE uint32_t transferCRCAddByte(const uint32_t crc, const byte_t byte } /// Do not forget to apply the output XOR when done, or use transferCRCCompute(). -UDPARD_PRIVATE uint32_t transferCRCAdd(const uint32_t crc, const size_t size, const void* const data) +static inline uint32_t transferCRCAdd(const uint32_t crc, const size_t size, const void* const data) { UDPARD_ASSERT((data != NULL) || (size == 0U)); uint32_t out = crc; @@ -210,7 +205,7 @@ UDPARD_PRIVATE uint32_t transferCRCAdd(const uint32_t crc, const size_t size, co return out; } -UDPARD_PRIVATE uint32_t transferCRCCompute(const size_t size, const void* const data) +static inline uint32_t transferCRCCompute(const size_t size, const void* const data) { return transferCRCAdd(TRANSFER_CRC_INITIAL, size, data) ^ TRANSFER_CRC_OUTPUT_XOR; } @@ -219,7 +214,7 @@ UDPARD_PRIVATE uint32_t transferCRCCompute(const size_t size, const void* const // ================================================= MEMORY RESOURCE ================================================= // ===================================================================================================================== -UDPARD_PRIVATE bool isValidMemoryResource(const UdpardMemoryResource* const memory) +static inline bool isValidMemoryResource(const UdpardMemoryResource* const memory) { return (memory != NULL) && (memory->allocate != NULL) && (memory->free != NULL); } @@ -250,13 +245,13 @@ typedef struct size_t count; } TxChain; -UDPARD_PRIVATE TxItem* txNewItem(UdpardMemoryResource* const memory, - const uint_least8_t dscp_value_per_priority[UDPARD_PRIORITY_MAX + 1U], - const UdpardMicrosecond deadline_usec, - const UdpardPriority priority, - const UdpardUDPIPEndpoint endpoint, - const size_t datagram_payload_size, - void* const user_transfer_reference) +static inline TxItem* txNewItem(UdpardMemoryResource* const memory, + const uint_least8_t dscp_value_per_priority[UDPARD_PRIORITY_MAX + 1U], + const UdpardMicrosecond deadline_usec, + const UdpardPriority priority, + const UdpardUDPIPEndpoint endpoint, + const size_t datagram_payload_size, + void* const user_transfer_reference) { UDPARD_ASSERT(memory != NULL); TxItem* const out = (TxItem*) memory->allocate(memory, sizeof(TxItem) + datagram_payload_size); @@ -284,8 +279,8 @@ UDPARD_PRIVATE TxItem* txNewItem(UdpardMemoryResource* const memory, /// Frames with identical weight are processed in the FIFO order. /// Frames with higher weight compare smaller (i.e., put on the left side of the tree). -UDPARD_PRIVATE int8_t txAVLPredicate(void* const user_reference, // NOSONAR Cavl API requires pointer to non-const. - const UdpardTreeNode* const node) +static inline int8_t txAVLPredicate(void* const user_reference, // NOSONAR Cavl API requires pointer to non-const. + const UdpardTreeNode* const node) { const TxItem* const target = (const TxItem*) user_reference; const TxItem* const other = (const TxItem*) (const void*) node; @@ -294,7 +289,7 @@ UDPARD_PRIVATE int8_t txAVLPredicate(void* const user_reference, // NOSONAR Cav } /// The primitive serialization functions are endian-agnostic. -UDPARD_PRIVATE byte_t* txSerializeU16(byte_t* const destination_buffer, const uint16_t value) +static inline byte_t* txSerializeU16(byte_t* const destination_buffer, const uint16_t value) { byte_t* p = destination_buffer; *p++ = (byte_t) (value & ByteMask); @@ -302,7 +297,7 @@ UDPARD_PRIVATE byte_t* txSerializeU16(byte_t* const destination_buffer, const ui return p; } -UDPARD_PRIVATE byte_t* txSerializeU32(byte_t* const destination_buffer, const uint32_t value) +static inline byte_t* txSerializeU32(byte_t* const destination_buffer, const uint32_t value) { byte_t* p = destination_buffer; for (size_t i = 0; i < sizeof(value); i++) // We sincerely hope that the compiler will use memcpy. @@ -312,7 +307,7 @@ UDPARD_PRIVATE byte_t* txSerializeU32(byte_t* const destination_buffer, const ui return p; } -UDPARD_PRIVATE byte_t* txSerializeU64(byte_t* const destination_buffer, const uint64_t value) +static inline byte_t* txSerializeU64(byte_t* const destination_buffer, const uint64_t value) { byte_t* p = destination_buffer; for (size_t i = 0; i < sizeof(value); i++) // We sincerely hope that the compiler will use memcpy. @@ -322,10 +317,10 @@ UDPARD_PRIVATE byte_t* txSerializeU64(byte_t* const destination_buffer, const ui return p; } -UDPARD_PRIVATE byte_t* txSerializeHeader(byte_t* const destination_buffer, - const TransferMetadata meta, - const uint32_t frame_index, - const bool end_of_transfer) +static inline byte_t* txSerializeHeader(byte_t* const destination_buffer, + const TransferMetadata meta, + const uint32_t frame_index, + const bool end_of_transfer) { byte_t* p = destination_buffer; *p++ = HEADER_VERSION; @@ -348,14 +343,14 @@ UDPARD_PRIVATE byte_t* txSerializeHeader(byte_t* const destination_buff /// Produces a chain of Tx queue items for later insertion into the Tx queue. The tail is NULL if OOM. /// The caller is responsible for freeing the memory allocated for the chain. -UDPARD_PRIVATE TxChain txMakeChain(UdpardMemoryResource* const memory, - const uint_least8_t dscp_value_per_priority[UDPARD_PRIORITY_MAX + 1U], - const size_t mtu, - const UdpardMicrosecond deadline_usec, - const TransferMetadata meta, - const UdpardUDPIPEndpoint endpoint, - const UdpardConstPayload payload, - void* const user_transfer_reference) +static inline TxChain txMakeChain(UdpardMemoryResource* const memory, + const uint_least8_t dscp_value_per_priority[UDPARD_PRIORITY_MAX + 1U], + const size_t mtu, + const UdpardMicrosecond deadline_usec, + const TransferMetadata meta, + const UdpardUDPIPEndpoint endpoint, + const UdpardConstPayload payload, + void* const user_transfer_reference) { UDPARD_ASSERT(memory != NULL); UDPARD_ASSERT(mtu > 0); @@ -419,12 +414,12 @@ UDPARD_PRIVATE TxChain txMakeChain(UdpardMemoryResource* const memory, return out; } -UDPARD_PRIVATE int32_t txPush(UdpardTx* const tx, - const UdpardMicrosecond deadline_usec, - const TransferMetadata meta, - const UdpardUDPIPEndpoint endpoint, - const UdpardConstPayload payload, - void* const user_transfer_reference) +static inline int32_t txPush(UdpardTx* const tx, + const UdpardMicrosecond deadline_usec, + const TransferMetadata meta, + const UdpardUDPIPEndpoint endpoint, + const UdpardConstPayload payload, + void* const user_transfer_reference) { UDPARD_ASSERT(tx != NULL); int32_t out = 0; // The number of frames enqueued or negated error. diff --git a/submodules/unity b/submodules/unity new file mode 160000 index 0000000..0b899ae --- /dev/null +++ b/submodules/unity @@ -0,0 +1 @@ +Subproject commit 0b899aec14d3a9abb2bf260ac355f0f28630a6a3 diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index ca43fa7..59b8bf1 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -6,15 +6,17 @@ cmake_minimum_required(VERSION 3.12) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_EXTENSIONS OFF) +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Woverloaded-virtual -Wnon-virtual-dtor -Wsign-promo") project(udpard_tests C CXX) enable_testing() set(CTEST_OUTPUT_ON_FAILURE ON) -set(library_dir "${CMAKE_SOURCE_DIR}/../libudpard") - set(NO_STATIC_ANALYSIS OFF CACHE BOOL "disable udpard static analysis") +set(library_dir "${CMAKE_SOURCE_DIR}/../libudpard") +set(unity_root "${CMAKE_CURRENT_SOURCE_DIR}/../submodules/unity") + # Use -DNO_STATIC_ANALYSIS=1 to suppress static analysis. # If not suppressed, the tools used here shall be available, otherwise the build will fail. if (NOT NO_STATIC_ANALYSIS) @@ -24,8 +26,6 @@ if (NOT NO_STATIC_ANALYSIS) message(FATAL_ERROR "Could not locate clang-tidy") endif () message(STATUS "Using clang-tidy: ${clang_tidy}") - set(CMAKE_C_CLANG_TIDY ${clang_tidy}) - set(CMAKE_CXX_CLANG_TIDY ${clang_tidy}) endif () # clang-format @@ -33,53 +33,58 @@ find_program(clang_format NAMES clang-format) if (NOT clang_format) message(STATUS "Could not locate clang-format") else () - file(GLOB format_files ${library_dir}/*.[ch] ${CMAKE_SOURCE_DIR}/*.[ch]pp) + file(GLOB_RECURSE format_files + ${library_dir}/*.[ch] + ${CMAKE_SOURCE_DIR}/src/*.[ch] + ${CMAKE_SOURCE_DIR}/src/*.[ch]pp) message(STATUS "Using clang-format: ${clang_format}; files: ${format_files}") add_custom_target(format COMMAND ${clang_format} -i -fallback-style=none -style=file --verbose ${format_files}) endif () -add_compile_options( - -Wall -Wextra -Werror -pedantic -Wdouble-promotion -Wswitch-enum -Wfloat-equal -Wundef - -Wconversion -Wtype-limits -Wsign-conversion -Wcast-align -) -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Woverloaded-virtual -Wnon-virtual-dtor -Wsign-promo") - function(gen_test name files compile_definitions compile_flags link_flags c_standard) - add_executable(${name} ${library_dir}/udpard.c ${files}) + # Unity + add_library("${name}_unity" STATIC "${unity_root}/src/unity.c") + target_include_directories("${name}_unity" SYSTEM PUBLIC "${unity_root}/src/") + target_compile_definitions("${name}_unity" PUBLIC + UNITY_INCLUDE_DOUBLE=1 UNITY_OUTPUT_COLOR=1 UNITY_SUPPORT_64=1 UNITY_SHORTHAND_AS_RAW=1) + set_target_properties( + "${name}_unity" + PROPERTIES + COMPILE_FLAGS "${compile_flags} \ + -Wno-sign-conversion -Wno-conversion -Wno-switch-enum -Wno-float-equal -Wno-double-promotion" + LINK_FLAGS "${link_flags}" + ) + # Target executable + add_executable(${name} ${files}) target_include_directories(${name} PUBLIC ${library_dir}) target_compile_definitions(${name} PUBLIC ${compile_definitions}) - # We're not using find_package(GTest) because this method can only find the package for one specific architecture, - # which is not what we want. We build tests for AMD64 and x86 separately, which requires linking against different - # GTest libraries located in different directories. Instead of doing it the hard way via find_package separately - # per architecture, we just pass the library names directly and let the linker figure out where to find them. - target_link_libraries(${name} pthread gtest gtest_main) + target_link_libraries(${name} "${name}_unity") set_target_properties( ${name} PROPERTIES - COMPILE_FLAGS "${compile_flags}" + COMPILE_FLAGS "${compile_flags} -Wall -Wextra -Werror -pedantic -Wdouble-promotion -Wswitch-enum \ + -Wfloat-equal -Wundef -Wconversion -Wtype-limits -Wsign-conversion -Wcast-align -Wmissing-declarations" LINK_FLAGS "${link_flags}" C_STANDARD "${c_standard}" C_EXTENSIONS OFF + C_CLANG_TIDY "${clang_tidy}" + CXX_CLANG_TIDY "${clang_tidy}" ) - add_test("run_${name}" "${name}" --gtest_random_seed=0 --gtest_color=yes) + add_test("run_${name}" "${name}") endfunction() -function(gen_test_matrix name files compile_definitions compile_flags link_flags) - gen_test("${name}_x64_c99" "${files}" "${compile_definitions}" "${compile_flags} -m64" "-m64 ${link_flags}" "99") - gen_test("${name}_x32_c99" "${files}" "${compile_definitions}" "${compile_flags} -m32" "-m32 ${link_flags}" "99") - gen_test("${name}_x64_c11" "${files}" "${compile_definitions}" "${compile_flags} -m64" "-m64 ${link_flags}" "11") - gen_test("${name}_x32_c11" "${files}" "${compile_definitions}" "${compile_flags} -m32" "-m32 ${link_flags}" "11") +function(gen_test_matrix name files) + gen_test("${name}_x64_c99" "${files}" "" "-m64" "-m64" "99") + gen_test("${name}_x32_c99" "${files}" "" "-m32" "-m32" "99") + gen_test("${name}_x64_c11" "${files}" "" "-m64" "-m64" "11") + gen_test("${name}_x32_c11" "${files}" "" "-m32" "-m32" "11") endfunction() -# Disable missing declaration warning to allow exposure of private definitions. -gen_test_matrix(test_private - "test_private_cavl.cpp;test_private_crc.cpp;test_private_tx.cpp;" - "-DUDPARD_CONFIG_HEADER=\"${CMAKE_CURRENT_SOURCE_DIR}/udpard_config_private.h\"" - "-Wno-missing-declarations" - "") - -gen_test_matrix(test_public - "test_self.cpp;test_public_tx.cpp;" - "" - "-Wmissing-declarations" - "") +# Add the test targets. +# Those that are written in C may #include to reach its internals; they are called "intrusive". +# The public interface tests may be written in C++ for convenience. +gen_test_matrix(test_helpers "src/test_helpers.c") +gen_test_matrix(test_cavl "src/test_cavl.cpp") +gen_test_matrix(test_tx "${library_dir}/udpard.c;src/test_tx.cpp") +gen_test_matrix(test_intrusive_crc "src/test_intrusive_crc.c") +gen_test_matrix(test_intrusive_tx "src/test_intrusive_tx.c") diff --git a/tests/exposed.hpp b/tests/exposed.hpp deleted file mode 100644 index a70865d..0000000 --- a/tests/exposed.hpp +++ /dev/null @@ -1,70 +0,0 @@ -/// This software is distributed under the terms of the MIT License. -/// Copyright (C) OpenCyphal Development Team -/// Copyright Amazon.com Inc. or its affiliates. -/// SPDX-License-Identifier: MIT - -#pragma once - -#include // Must be always included first. -#include -#include -#include -#include - -/// Definitions that are not exposed by the library but that are needed for testing. -/// Please keep them in sync with the library by manually updating as necessary. -namespace exposed -{ -using byte_t = std::uint_least8_t; - -constexpr std::size_t HeaderSize = 24U; - -struct TransferMetadata final -{ - UdpardPriority priority; - UdpardNodeID src_node_id; - UdpardNodeID dst_node_id; - std::uint16_t data_specifier; - UdpardTransferID transfer_id; -}; - -struct TxItem final : public UdpardTxItem -{ - UdpardPriority priority; - // flex array not included -}; - -struct TxChain final -{ - TxItem* head; - TxItem* tail; - std::size_t count; -}; - -extern "C" { -std::uint16_t headerCRCCompute(const std::size_t size, const void* const data); - -std::uint32_t transferCRCAdd(const std::uint32_t crc, const std::size_t size, const void* const data); - -byte_t* txSerializeHeader(byte_t* const destination_buffer, - const TransferMetadata meta, - const std::uint32_t frame_index, - const bool end_of_transfer); - -TxChain txMakeChain(UdpardMemoryResource* const memory, - const std::uint_least8_t dscp_value_per_priority[UDPARD_PRIORITY_MAX + 1U], - const std::size_t mtu, - const UdpardMicrosecond deadline_usec, - const TransferMetadata meta, - const UdpardUDPIPEndpoint endpoint, - const UdpardConstPayload payload, - void* const user_transfer_reference); - -std::int32_t txPush(UdpardTx* const tx, - const UdpardMicrosecond deadline_usec, - const TransferMetadata meta, - const UdpardUDPIPEndpoint endpoint, - const UdpardConstPayload payload, - void* const user_transfer_reference); -} -} // namespace exposed diff --git a/tests/helpers.hpp b/tests/helpers.hpp deleted file mode 100644 index 676869d..0000000 --- a/tests/helpers.hpp +++ /dev/null @@ -1,187 +0,0 @@ -// This software is distributed under the terms of the MIT License. -// Copyright (c) 2016 Cyphal Development Team. -/// Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. - -#pragma once - -#include // Shall always be included first. -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#if !(defined(UDPARD_VERSION_MAJOR) && defined(UDPARD_VERSION_MINOR)) -# error "Library version not defined" -#endif - -#if !(defined(UDPARD_CYPHAL_SPECIFICATION_VERSION_MAJOR) && defined(UDPARD_CYPHAL_SPECIFICATION_VERSION_MINOR)) -# error "Cyphal specification version not defined" -#endif - -namespace helpers -{ -namespace dummy_allocator -{ -inline auto allocate(UdpardMemoryResource* const self, const std::size_t amount) -> void* -{ - (void) self; - (void) amount; - return nullptr; -} - -inline void free(UdpardMemoryResource* const self, const size_t size, void* const pointer) -{ - (void) self; - (void) size; - (void) pointer; -} -} // namespace dummy_allocator - -/// We can't use the recommended true random std::random because it cannot be seeded by Catch2 (the testing framework). -template -inline auto getRandomNatural(const T upper_open) -> T -{ - return static_cast(static_cast(std::rand()) % upper_open); -} - -template -inline void traverse(const UdpardTreeNode* const root, const F& fun) -{ - if (root != nullptr) - { - traverse(root->lr[0], fun); - fun(root); - traverse(root->lr[1], fun); - } -} - -/// An allocator that sits on top of the standard malloc() providing additional testing capabilities. -/// It allows the user to specify the maximum amount of memory that can be allocated; further requests will emulate OOM. -/// It also performs correctness checks on the memory use. -class TestAllocator final : public UdpardMemoryResource -{ -public: - TestAllocator() : - UdpardMemoryResource{.allocate = &TestAllocator::trampolineAllocate, - .free = &TestAllocator::trampolineFree, - .user_reference = this} - {} - TestAllocator(const TestAllocator&) = delete; - TestAllocator(const TestAllocator&&) = delete; - auto operator=(const TestAllocator&) -> TestAllocator& = delete; - auto operator=(const TestAllocator&&) -> TestAllocator& = delete; - - virtual ~TestAllocator() - { - const std::unique_lock locker(lock_); - for (const auto& pair : allocated_) - { - std::free(pair.first - canary_.size()); - } - } - - [[nodiscard]] auto allocate(const std::size_t size) -> void* - { - const std::unique_lock locker(lock_); - std::uint8_t* p = nullptr; - if ((size > 0U) && ((getTotalAllocatedAmount() + size) <= ceiling_)) - { - const auto size_with_canaries = size + canary_.size() * 2U; - p = static_cast(std::malloc(size_with_canaries)); - if (p == nullptr) - { - throw std::bad_alloc(); // This is a test suite failure, not a failed test. Mind the difference. - } - p += canary_.size(); - std::generate_n(p, size, [] { return static_cast(getRandomNatural(256U)); }); - std::memcpy(p - canary_.size(), canary_.begin(), canary_.size()); - std::memcpy(p + size, canary_.begin(), canary_.size()); - allocated_.emplace(p, size); - } - return p; - } - - void free(const std::size_t size, void* const pointer) - { - if (pointer != nullptr) - { - const std::unique_lock locker(lock_); - const auto it = allocated_.find(static_cast(pointer)); - if (it == std::end(allocated_)) // Catch an attempt to deallocate memory that is not allocated. - { - throw std::logic_error("Attempted to deallocate memory that was never allocated; ptr=" + - std::to_string(reinterpret_cast(pointer))); - } - const auto [p, true_size] = *it; - if (size != true_size) - { - throw std::logic_error("Attempted to deallocate memory with a wrong size; ptr=" + - std::to_string(reinterpret_cast(pointer))); - } - if ((0 != std::memcmp(p - canary_.size(), canary_.begin(), canary_.size())) || - (0 != std::memcmp(p + true_size, canary_.begin(), canary_.size()))) - { - throw std::logic_error("Dead canary detected at ptr=" + - std::to_string(reinterpret_cast(pointer))); - } - std::generate_n(p - canary_.size(), // Damage the memory to make sure it's not used after deallocation. - true_size + canary_.size() * 2U, - []() { return static_cast(getRandomNatural(256U)); }); - std::free(p - canary_.size()); - allocated_.erase(it); - } - } - - [[nodiscard]] auto getNumAllocatedFragments() const - { - const std::unique_lock locker(lock_); - return std::size(allocated_); - } - - [[nodiscard]] auto getTotalAllocatedAmount() const -> std::size_t - { - const std::unique_lock locker(lock_); - std::size_t out = 0U; - for (const auto& pair : allocated_) - { - out += pair.second; - } - return out; - } - - [[nodiscard]] auto getAllocationCeiling() const { return static_cast(ceiling_); } - void setAllocationCeiling(const std::size_t amount) { ceiling_ = amount; } - -private: - static auto makeCanary() -> std::array - { - std::array out{}; - std::generate_n(out.begin(), out.size(), []() { return static_cast(getRandomNatural(256U)); }); - return out; - } - - static auto trampolineAllocate(UdpardMemoryResource* const self, const size_t size) -> void* - { - return static_cast(self->user_reference)->allocate(size); - } - - static void trampolineFree(UdpardMemoryResource* const self, const size_t size, void* const pointer) - { - return static_cast(self->user_reference)->free(size, pointer); - } - - const std::array canary_ = makeCanary(); - - mutable std::recursive_mutex lock_; - std::unordered_map allocated_; - std::atomic ceiling_ = std::numeric_limits::max(); -}; - -} // namespace helpers diff --git a/tests/src/helpers.h b/tests/src/helpers.h new file mode 100644 index 0000000..e1eaf86 --- /dev/null +++ b/tests/src/helpers.h @@ -0,0 +1,149 @@ +// This software is distributed under the terms of the MIT License. +// Copyright (c) 2016 Cyphal Development Team. +/// Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + +#pragma once + +#include // Shall always be included first. +#include +#include +#include + +#if !(defined(UDPARD_VERSION_MAJOR) && defined(UDPARD_VERSION_MINOR)) +# error "Library version not defined" +#endif + +#if !(defined(UDPARD_CYPHAL_SPECIFICATION_VERSION_MAJOR) && defined(UDPARD_CYPHAL_SPECIFICATION_VERSION_MINOR)) +# error "Cyphal specification version not defined" +#endif + +// This is only needed to tell static analyzers that the code that follows is not C++. +#ifdef __cplusplus +extern "C" { +#endif + +#define TEST_PANIC(message) \ + do \ + { \ + printf("%s:%u: PANIC: %s\n", __FILE__, (unsigned) __LINE__, message); \ + abort(); \ + } while (0) +#define TEST_PANIC_UNLESS(condition) \ + do \ + { \ + if (!(condition)) \ + { \ + TEST_PANIC(#condition); \ + } \ + } while (0) + +static inline void* dummyAllocatorAllocate(UdpardMemoryResource* const self, const size_t size) +{ + (void) self; + (void) size; + return NULL; +} + +static inline void dummyAllocatorFree(UdpardMemoryResource* const self, const size_t size, void* const pointer) +{ + (void) size; + TEST_PANIC_UNLESS(self != NULL); + TEST_PANIC_UNLESS(pointer == NULL); +} + +/// The instrumented allocator tracks memory consumption, checks for heap corruption, and can be configured to fail +/// allocations above a certain threshold. +#define INSTRUMENTED_ALLOCATOR_CANARY_SIZE 1024U +typedef struct +{ + UdpardMemoryResource base; + uint_least8_t canary[INSTRUMENTED_ALLOCATOR_CANARY_SIZE]; + /// The limit can be changed at any moment to control the maximum amount of memory that can be allocated. + /// It may be set to a value less than the currently allocated amount. + size_t limit_bytes; + /// The current state of the allocator. + size_t allocated_fragments; + size_t allocated_bytes; +} InstrumentedAllocator; + +static inline void* instrumentedAllocatorAllocate(UdpardMemoryResource* const base, const size_t size) +{ + InstrumentedAllocator* const self = (InstrumentedAllocator*) base; + TEST_PANIC_UNLESS(self->base.allocate == &instrumentedAllocatorAllocate); + void* result = NULL; + if ((size > 0U) && ((self->allocated_bytes + size) <= self->limit_bytes)) + { + const size_t size_with_canaries = size + ((size_t) INSTRUMENTED_ALLOCATOR_CANARY_SIZE * 2U); + void* origin = malloc(size_with_canaries); + TEST_PANIC_UNLESS(origin != NULL); + *((size_t*) origin) = size; + uint_least8_t* p = ((uint_least8_t*) origin) + sizeof(size_t); + result = ((uint_least8_t*) origin) + INSTRUMENTED_ALLOCATOR_CANARY_SIZE; + for (size_t i = sizeof(size_t); i < INSTRUMENTED_ALLOCATOR_CANARY_SIZE; i++) // Fill the front canary. + { + *p++ = self->canary[i]; + } + for (size_t i = 0; i < size; i++) // Randomize the allocated fragment. + { + *p++ = (uint_least8_t) (rand() % (UINT_LEAST8_MAX + 1)); + } + for (size_t i = 0; i < INSTRUMENTED_ALLOCATOR_CANARY_SIZE; i++) // Fill the back canary. + { + *p++ = self->canary[i]; + } + self->allocated_fragments++; + self->allocated_bytes += size; + } + return result; +} + +static inline void instrumentedAllocatorFree(UdpardMemoryResource* const base, const size_t size, void* const pointer) +{ + InstrumentedAllocator* const self = (InstrumentedAllocator*) base; + TEST_PANIC_UNLESS(self->base.allocate == &instrumentedAllocatorAllocate); + TEST_PANIC_UNLESS(self->base.free == &instrumentedAllocatorFree); + if (pointer != NULL) + { + uint_least8_t* p = ((uint_least8_t*) pointer) - INSTRUMENTED_ALLOCATOR_CANARY_SIZE; + void* const origin = p; + const size_t true_size = *((const size_t*) origin); + TEST_PANIC_UNLESS(size == true_size); + p += sizeof(size_t); + for (size_t i = sizeof(size_t); i < INSTRUMENTED_ALLOCATOR_CANARY_SIZE; i++) // Check the front canary. + { + TEST_PANIC_UNLESS(*p++ == self->canary[i]); + } + for (size_t i = 0; i < size; i++) // Destroy the returned memory to prevent use-after-free. + { + *p++ = (uint_least8_t) (rand() % (UINT_LEAST8_MAX + 1)); + } + for (size_t i = 0; i < INSTRUMENTED_ALLOCATOR_CANARY_SIZE; i++) // Check the back canary. + { + TEST_PANIC_UNLESS(*p++ == self->canary[i]); + } + free(origin); + TEST_PANIC_UNLESS(self->allocated_fragments > 0U); + self->allocated_fragments--; + TEST_PANIC_UNLESS(self->allocated_bytes >= size); + self->allocated_bytes -= size; + } +} + +/// By default, the limit is unrestricted (set to the maximum possible value). +static inline void instrumentedAllocatorNew(InstrumentedAllocator* const self) +{ + self->base.allocate = &instrumentedAllocatorAllocate; + self->base.free = &instrumentedAllocatorFree; + self->base.user_reference = NULL; + for (size_t i = 0; i < INSTRUMENTED_ALLOCATOR_CANARY_SIZE; i++) + { + self->canary[i] = (uint_least8_t) (rand() % (UINT_LEAST8_MAX + 1)); + } + self->limit_bytes = SIZE_MAX; + self->allocated_fragments = 0U; + self->allocated_bytes = 0U; +} + +#ifdef __cplusplus +} +#endif diff --git a/tests/hexdump.hpp b/tests/src/hexdump.hpp similarity index 99% rename from tests/hexdump.hpp rename to tests/src/hexdump.hpp index c1ccc2b..3e44e63 100644 --- a/tests/hexdump.hpp +++ b/tests/src/hexdump.hpp @@ -5,6 +5,7 @@ /// Author: Pavel Kirienko #include +#include #include #include diff --git a/tests/test_private_cavl.cpp b/tests/src/test_cavl.cpp similarity index 56% rename from tests/test_private_cavl.cpp rename to tests/src/test_cavl.cpp index 7cb3e38..c944f09 100644 --- a/tests/test_private_cavl.cpp +++ b/tests/src/test_cavl.cpp @@ -3,10 +3,11 @@ // These tests have been adapted from the Cavl test suite that you can find at https://github.com/pavel-kirienko/cavl #include <_udpard_cavl.h> -#include +#include #include #include #include +#include #include #include #include @@ -23,7 +24,9 @@ template struct Node final : Cavl { explicit Node(const T val) : Cavl{Cavl{}}, value(val) {} + Node(const Cavl& cv, const T val) : Cavl{cv}, value(val) {} + Node() : Cavl{Cavl{}} {} T value{}; @@ -42,6 +45,7 @@ struct Node final : Cavl } auto min() -> Node* { return static_cast(cavlFindExtremum(this, false)); } + auto max() -> Node* { return static_cast(cavlFindExtremum(this, true)); } auto operator=(const Cavl& cv) -> Node& @@ -82,6 +86,7 @@ auto search(Node** const root, const Predicate& predicate, const Factory& fac Cavl* const out = cavlSearch(reinterpret_cast(root), &refs, &Refs::callPredicate, &Refs::callFactory); return static_cast*>(out); } + template auto search(Node** const root, const Predicate& predicate) -> Node* { @@ -106,7 +111,7 @@ auto getHeight(const Node* const n) -> std::uint8_t // NOLINT recursion template void print(const Node* const nd, const std::uint8_t depth = 0, const char marker = 'T') // NOLINT recursion { - ASSERT_TRUE(10 > getHeight(nd)); // Fail early for malformed cyclic trees, do not overwhelm stdout. + TEST_ASSERT(10 > getHeight(nd)); // Fail early for malformed cyclic trees, do not overwhelm stdout. if (nd != nullptr) { print(static_cast*>(nd->lr[0]), static_cast(depth + 1U), 'L'); @@ -203,9 +208,8 @@ auto findBrokenBalanceFactor(const Node* const n) -> const Cavl* // NOLINT r } return nullptr; } -} // namespace -TEST(Cavl, CheckAscension) +void testCheckAscension() { using N = Node; N t{2}; @@ -216,23 +220,23 @@ TEST(Cavl, CheckAscension) t.lr[0] = &l; t.lr[1] = &r; r.lr[1] = &rr; - ASSERT_TRUE(4 == checkAscension(&t)); - ASSERT_TRUE(3 == getHeight(&t)); + TEST_ASSERT(4 == checkAscension(&t)); + TEST_ASSERT(3 == getHeight(&t)); // Break the arrangement and make sure the breakage is detected. t.lr[1] = &l; t.lr[0] = &r; - ASSERT_TRUE(4 != checkAscension(&t)); - ASSERT_TRUE(3 == getHeight(&t)); - ASSERT_TRUE(&t == findBrokenBalanceFactor(&t)); // All zeros, incorrect. + TEST_ASSERT(4 != checkAscension(&t)); + TEST_ASSERT(3 == getHeight(&t)); + TEST_ASSERT(&t == findBrokenBalanceFactor(&t)); // All zeros, incorrect. r.lr[1] = nullptr; std::cout << __LINE__ << ": " << static_cast(getHeight(&t)) << std::endl; print(&t); std::cout << __LINE__ << ": " << static_cast(getHeight(&t)) << std::endl; - ASSERT_EQ(2, getHeight(&t)); - ASSERT_TRUE(nullptr == findBrokenBalanceFactor(&t)); // Balanced now as we removed one node. + TEST_ASSERT_EQUAL_size_t(2, getHeight(&t)); + TEST_ASSERT(nullptr == findBrokenBalanceFactor(&t)); // Balanced now as we removed one node. } -TEST(Cavl, Rotation) +void testRotation() { using N = Node; // Original state: @@ -256,29 +260,29 @@ TEST(Cavl, Rotation) a.up = &x; std::cout << "Before rotation:\n"; - ASSERT_TRUE(nullptr == findBrokenAncestry(&x)); + TEST_ASSERT(nullptr == findBrokenAncestry(&x)); print(&x); std::cout << "After left rotation:\n"; cavlPrivateRotate(&x, false); // z is now the root - ASSERT_TRUE(nullptr == findBrokenAncestry(&z)); + TEST_ASSERT(nullptr == findBrokenAncestry(&z)); print(&z); - ASSERT_TRUE(&a == x.lr[0]); - ASSERT_TRUE(&b == x.lr[1]); - ASSERT_TRUE(&x == z.lr[0]); - ASSERT_TRUE(&c == z.lr[1]); + TEST_ASSERT(&a == x.lr[0]); + TEST_ASSERT(&b == x.lr[1]); + TEST_ASSERT(&x == z.lr[0]); + TEST_ASSERT(&c == z.lr[1]); std::cout << "After right rotation, back into the original configuration:\n"; cavlPrivateRotate(&z, true); // x is now the root - ASSERT_TRUE(nullptr == findBrokenAncestry(&x)); + TEST_ASSERT(nullptr == findBrokenAncestry(&x)); print(&x); - ASSERT_TRUE(&a == x.lr[0]); - ASSERT_TRUE(&z == x.lr[1]); - ASSERT_TRUE(&b == z.lr[0]); - ASSERT_TRUE(&c == z.lr[1]); + TEST_ASSERT(&a == x.lr[0]); + TEST_ASSERT(&z == x.lr[1]); + TEST_ASSERT(&b == z.lr[0]); + TEST_ASSERT(&c == z.lr[1]); } -TEST(Cavl, BalancingA) +void testBalancingA() { using N = Node; // Double left-right rotation. @@ -303,32 +307,32 @@ TEST(Cavl, BalancingA) y.lr[0] = &f; y.lr[1] = &g; print(&x); - ASSERT_TRUE(nullptr == findBrokenAncestry(&x)); - ASSERT_TRUE(&x == cavlPrivateAdjustBalance(&x, false)); // bf = -1, same topology - ASSERT_TRUE(-1 == x.bf); - ASSERT_TRUE(&z == cavlPrivateAdjustBalance(&z, true)); // bf = +1, same topology - ASSERT_TRUE(+1 == z.bf); - ASSERT_TRUE(&y == cavlPrivateAdjustBalance(&x, false)); // bf = -2, rotation needed + TEST_ASSERT(nullptr == findBrokenAncestry(&x)); + TEST_ASSERT(&x == cavlPrivateAdjustBalance(&x, false)); // bf = -1, same topology + TEST_ASSERT(-1 == x.bf); + TEST_ASSERT(&z == cavlPrivateAdjustBalance(&z, true)); // bf = +1, same topology + TEST_ASSERT(+1 == z.bf); + TEST_ASSERT(&y == cavlPrivateAdjustBalance(&x, false)); // bf = -2, rotation needed print(&y); - ASSERT_TRUE(nullptr == findBrokenBalanceFactor(&y)); // Should be balanced now. - ASSERT_TRUE(nullptr == findBrokenAncestry(&y)); - ASSERT_TRUE(&z == y.lr[0]); - ASSERT_TRUE(&x == y.lr[1]); - ASSERT_TRUE(&d == z.lr[0]); - ASSERT_TRUE(&f == z.lr[1]); - ASSERT_TRUE(&g == x.lr[0]); - ASSERT_TRUE(&c == x.lr[1]); - ASSERT_TRUE(Zz == d.lr[0]); - ASSERT_TRUE(Zz == d.lr[1]); - ASSERT_TRUE(Zz == f.lr[0]); - ASSERT_TRUE(Zz == f.lr[1]); - ASSERT_TRUE(Zz == g.lr[0]); - ASSERT_TRUE(Zz == g.lr[1]); - ASSERT_TRUE(Zz == c.lr[0]); - ASSERT_TRUE(Zz == c.lr[1]); + TEST_ASSERT(nullptr == findBrokenBalanceFactor(&y)); // Should be balanced now. + TEST_ASSERT(nullptr == findBrokenAncestry(&y)); + TEST_ASSERT(&z == y.lr[0]); + TEST_ASSERT(&x == y.lr[1]); + TEST_ASSERT(&d == z.lr[0]); + TEST_ASSERT(&f == z.lr[1]); + TEST_ASSERT(&g == x.lr[0]); + TEST_ASSERT(&c == x.lr[1]); + TEST_ASSERT(Zz == d.lr[0]); + TEST_ASSERT(Zz == d.lr[1]); + TEST_ASSERT(Zz == f.lr[0]); + TEST_ASSERT(Zz == f.lr[1]); + TEST_ASSERT(Zz == g.lr[0]); + TEST_ASSERT(Zz == g.lr[1]); + TEST_ASSERT(Zz == c.lr[0]); + TEST_ASSERT(Zz == c.lr[1]); } -TEST(Cavl, BalancingB) +void testBalancingB() { using N = Node; // Without F the handling of Z and Y is more complex; Z flips the sign of its balance factor: @@ -352,32 +356,32 @@ TEST(Cavl, BalancingB) y = {{&z, {Zz, &g}, 0}, 5}; // bf = +1 g = {{&y, {Zz, Zz}, 0}, 7}; print(&x); - ASSERT_TRUE(nullptr == findBrokenAncestry(&x)); - ASSERT_TRUE(&x == cavlPrivateAdjustBalance(&x, false)); // bf = -1, same topology - ASSERT_TRUE(-1 == x.bf); - ASSERT_TRUE(&z == cavlPrivateAdjustBalance(&z, true)); // bf = +1, same topology - ASSERT_TRUE(+1 == z.bf); - ASSERT_TRUE(&y == cavlPrivateAdjustBalance(&y, true)); // bf = +1, same topology - ASSERT_TRUE(+1 == y.bf); - ASSERT_TRUE(&y == cavlPrivateAdjustBalance(&x, false)); // bf = -2, rotation needed + TEST_ASSERT(nullptr == findBrokenAncestry(&x)); + TEST_ASSERT(&x == cavlPrivateAdjustBalance(&x, false)); // bf = -1, same topology + TEST_ASSERT(-1 == x.bf); + TEST_ASSERT(&z == cavlPrivateAdjustBalance(&z, true)); // bf = +1, same topology + TEST_ASSERT(+1 == z.bf); + TEST_ASSERT(&y == cavlPrivateAdjustBalance(&y, true)); // bf = +1, same topology + TEST_ASSERT(+1 == y.bf); + TEST_ASSERT(&y == cavlPrivateAdjustBalance(&x, false)); // bf = -2, rotation needed print(&y); - ASSERT_TRUE(nullptr == findBrokenBalanceFactor(&y)); // Should be balanced now. - ASSERT_TRUE(nullptr == findBrokenAncestry(&y)); - ASSERT_TRUE(&z == y.lr[0]); - ASSERT_TRUE(&x == y.lr[1]); - ASSERT_TRUE(&d == z.lr[0]); - ASSERT_TRUE(Zz == z.lr[1]); - ASSERT_TRUE(&g == x.lr[0]); - ASSERT_TRUE(&c == x.lr[1]); - ASSERT_TRUE(Zz == d.lr[0]); - ASSERT_TRUE(Zz == d.lr[1]); - ASSERT_TRUE(Zz == g.lr[0]); - ASSERT_TRUE(Zz == g.lr[1]); - ASSERT_TRUE(Zz == c.lr[0]); - ASSERT_TRUE(Zz == c.lr[1]); + TEST_ASSERT(nullptr == findBrokenBalanceFactor(&y)); // Should be balanced now. + TEST_ASSERT(nullptr == findBrokenAncestry(&y)); + TEST_ASSERT(&z == y.lr[0]); + TEST_ASSERT(&x == y.lr[1]); + TEST_ASSERT(&d == z.lr[0]); + TEST_ASSERT(Zz == z.lr[1]); + TEST_ASSERT(&g == x.lr[0]); + TEST_ASSERT(&c == x.lr[1]); + TEST_ASSERT(Zz == d.lr[0]); + TEST_ASSERT(Zz == d.lr[1]); + TEST_ASSERT(Zz == g.lr[0]); + TEST_ASSERT(Zz == g.lr[1]); + TEST_ASSERT(Zz == c.lr[0]); + TEST_ASSERT(Zz == c.lr[1]); } -TEST(Cavl, BalancingC) +void testBalancingC() { using N = Node; // Both X and Z are heavy on the same side. @@ -403,32 +407,32 @@ TEST(Cavl, BalancingC) f = {{&d, {Zz, Zz}, 0}, 6}; g = {{&d, {Zz, Zz}, 0}, 7}; print(&x); - ASSERT_TRUE(nullptr == findBrokenAncestry(&x)); - ASSERT_TRUE(&x == cavlPrivateAdjustBalance(&x, false)); // bf = -1, same topology - ASSERT_TRUE(-1 == x.bf); - ASSERT_TRUE(&z == cavlPrivateAdjustBalance(&z, false)); // bf = -1, same topology - ASSERT_TRUE(-1 == z.bf); - ASSERT_TRUE(&z == cavlPrivateAdjustBalance(&x, false)); + TEST_ASSERT(nullptr == findBrokenAncestry(&x)); + TEST_ASSERT(&x == cavlPrivateAdjustBalance(&x, false)); // bf = -1, same topology + TEST_ASSERT(-1 == x.bf); + TEST_ASSERT(&z == cavlPrivateAdjustBalance(&z, false)); // bf = -1, same topology + TEST_ASSERT(-1 == z.bf); + TEST_ASSERT(&z == cavlPrivateAdjustBalance(&x, false)); print(&z); - ASSERT_TRUE(nullptr == findBrokenBalanceFactor(&z)); - ASSERT_TRUE(nullptr == findBrokenAncestry(&z)); - ASSERT_TRUE(&d == z.lr[0]); - ASSERT_TRUE(&x == z.lr[1]); - ASSERT_TRUE(&f == d.lr[0]); - ASSERT_TRUE(&g == d.lr[1]); - ASSERT_TRUE(&y == x.lr[0]); - ASSERT_TRUE(&c == x.lr[1]); - ASSERT_TRUE(Zz == f.lr[0]); - ASSERT_TRUE(Zz == f.lr[1]); - ASSERT_TRUE(Zz == g.lr[0]); - ASSERT_TRUE(Zz == g.lr[1]); - ASSERT_TRUE(Zz == y.lr[0]); - ASSERT_TRUE(Zz == y.lr[1]); - ASSERT_TRUE(Zz == c.lr[0]); - ASSERT_TRUE(Zz == c.lr[1]); + TEST_ASSERT(nullptr == findBrokenBalanceFactor(&z)); + TEST_ASSERT(nullptr == findBrokenAncestry(&z)); + TEST_ASSERT(&d == z.lr[0]); + TEST_ASSERT(&x == z.lr[1]); + TEST_ASSERT(&f == d.lr[0]); + TEST_ASSERT(&g == d.lr[1]); + TEST_ASSERT(&y == x.lr[0]); + TEST_ASSERT(&c == x.lr[1]); + TEST_ASSERT(Zz == f.lr[0]); + TEST_ASSERT(Zz == f.lr[1]); + TEST_ASSERT(Zz == g.lr[0]); + TEST_ASSERT(Zz == g.lr[1]); + TEST_ASSERT(Zz == y.lr[0]); + TEST_ASSERT(Zz == y.lr[1]); + TEST_ASSERT(Zz == c.lr[0]); + TEST_ASSERT(Zz == c.lr[1]); } -TEST(Cavl, RetracingOnGrowth) +void testRetracingOnGrowth() { using N = Node; std::array t{}; @@ -450,46 +454,46 @@ TEST(Cavl, RetracingOnGrowth) t[40] = {&t[30], {Zzzzzz, Zzzzzz}, 00}; t[10] = {&t[20], {Zzzzzz, Zzzzzz}, 00}; print(&t[50]); // The tree is imbalanced because we just added 1 and are about to retrace it. - ASSERT_TRUE(nullptr == findBrokenAncestry(&t[50])); - ASSERT_TRUE(6 == checkAscension(&t[50])); - ASSERT_TRUE(&t[30] == cavlPrivateRetraceOnGrowth(&t[10])); + TEST_ASSERT(nullptr == findBrokenAncestry(&t[50])); + TEST_ASSERT(6 == checkAscension(&t[50])); + TEST_ASSERT(&t[30] == cavlPrivateRetraceOnGrowth(&t[10])); std::puts("ADD 10:"); print(&t[30]); // This is the new root. - ASSERT_TRUE(&t[20] == t[30].lr[0]); - ASSERT_TRUE(&t[50] == t[30].lr[1]); - ASSERT_TRUE(&t[10] == t[20].lr[0]); - ASSERT_TRUE(Zzzzzz == t[20].lr[1]); - ASSERT_TRUE(&t[40] == t[50].lr[0]); - ASSERT_TRUE(&t[60] == t[50].lr[1]); - ASSERT_TRUE(Zzzzzz == t[10].lr[0]); - ASSERT_TRUE(Zzzzzz == t[10].lr[1]); - ASSERT_TRUE(Zzzzzz == t[40].lr[0]); - ASSERT_TRUE(Zzzzzz == t[40].lr[1]); - ASSERT_TRUE(Zzzzzz == t[60].lr[0]); - ASSERT_TRUE(Zzzzzz == t[60].lr[1]); - ASSERT_TRUE(-1 == t[20].bf); - ASSERT_TRUE(+0 == t[30].bf); - ASSERT_TRUE(nullptr == findBrokenAncestry(&t[30])); - ASSERT_TRUE(nullptr == findBrokenBalanceFactor(&t[30])); - ASSERT_TRUE(6 == checkAscension(&t[30])); + TEST_ASSERT(&t[20] == t[30].lr[0]); + TEST_ASSERT(&t[50] == t[30].lr[1]); + TEST_ASSERT(&t[10] == t[20].lr[0]); + TEST_ASSERT(Zzzzzz == t[20].lr[1]); + TEST_ASSERT(&t[40] == t[50].lr[0]); + TEST_ASSERT(&t[60] == t[50].lr[1]); + TEST_ASSERT(Zzzzzz == t[10].lr[0]); + TEST_ASSERT(Zzzzzz == t[10].lr[1]); + TEST_ASSERT(Zzzzzz == t[40].lr[0]); + TEST_ASSERT(Zzzzzz == t[40].lr[1]); + TEST_ASSERT(Zzzzzz == t[60].lr[0]); + TEST_ASSERT(Zzzzzz == t[60].lr[1]); + TEST_ASSERT(-1 == t[20].bf); + TEST_ASSERT(+0 == t[30].bf); + TEST_ASSERT(nullptr == findBrokenAncestry(&t[30])); + TEST_ASSERT(nullptr == findBrokenBalanceFactor(&t[30])); + TEST_ASSERT(6 == checkAscension(&t[30])); // Add a new child under 20 and ensure that retracing stops at 20 because it becomes perfectly balanced: // 30 // / ` // 20 50 // / ` / ` // 10 21 40 60 - ASSERT_TRUE(nullptr == findBrokenAncestry(&t[30])); - ASSERT_TRUE(nullptr == findBrokenBalanceFactor(&t[30])); + TEST_ASSERT(nullptr == findBrokenAncestry(&t[30])); + TEST_ASSERT(nullptr == findBrokenBalanceFactor(&t[30])); t[21] = {&t[20], {Zzzzzz, Zzzzzz}, 0}; t[20].lr[1] = &t[21]; - ASSERT_TRUE(nullptr == cavlPrivateRetraceOnGrowth(&t[21])); // Root not reached, NULL returned. + TEST_ASSERT(nullptr == cavlPrivateRetraceOnGrowth(&t[21])); // Root not reached, NULL returned. std::puts("ADD 21:"); print(&t[30]); - ASSERT_TRUE(0 == t[20].bf); - ASSERT_TRUE(0 == t[30].bf); - ASSERT_TRUE(nullptr == findBrokenAncestry(&t[30])); - ASSERT_TRUE(nullptr == findBrokenBalanceFactor(&t[30])); - ASSERT_TRUE(7 == checkAscension(&t[30])); + TEST_ASSERT(0 == t[20].bf); + TEST_ASSERT(0 == t[30].bf); + TEST_ASSERT(nullptr == findBrokenAncestry(&t[30])); + TEST_ASSERT(nullptr == findBrokenBalanceFactor(&t[30])); + TEST_ASSERT(7 == checkAscension(&t[30])); // 30 // / ` // 20 50 @@ -544,97 +548,97 @@ TEST(Cavl, RetracingOnGrowth) // / / ` // 10 18 21 std::puts("ADD 15:"); - ASSERT_TRUE(nullptr == findBrokenAncestry(&t[30])); - ASSERT_TRUE(nullptr == findBrokenBalanceFactor(&t[30])); - ASSERT_TRUE(7 == checkAscension(&t[30])); + TEST_ASSERT(nullptr == findBrokenAncestry(&t[30])); + TEST_ASSERT(nullptr == findBrokenBalanceFactor(&t[30])); + TEST_ASSERT(7 == checkAscension(&t[30])); t[15] = {&t[10], {Zzzzzz, Zzzzzz}, 0}; t[10].lr[1] = &t[15]; - ASSERT_TRUE(&t[30] == cavlPrivateRetraceOnGrowth(&t[15])); // Same root, its balance becomes -1. + TEST_ASSERT(&t[30] == cavlPrivateRetraceOnGrowth(&t[15])); // Same root, its balance becomes -1. print(&t[30]); - ASSERT_TRUE(+1 == t[10].bf); - ASSERT_TRUE(-1 == t[20].bf); - ASSERT_TRUE(-1 == t[30].bf); - ASSERT_TRUE(nullptr == findBrokenAncestry(&t[30])); - ASSERT_TRUE(nullptr == findBrokenBalanceFactor(&t[30])); - ASSERT_TRUE(8 == checkAscension(&t[30])); + TEST_ASSERT(+1 == t[10].bf); + TEST_ASSERT(-1 == t[20].bf); + TEST_ASSERT(-1 == t[30].bf); + TEST_ASSERT(nullptr == findBrokenAncestry(&t[30])); + TEST_ASSERT(nullptr == findBrokenBalanceFactor(&t[30])); + TEST_ASSERT(8 == checkAscension(&t[30])); std::puts("ADD 17:"); t[17] = {&t[15], {Zzzzzz, Zzzzzz}, 0}; t[15].lr[1] = &t[17]; - ASSERT_TRUE(nullptr == cavlPrivateRetraceOnGrowth(&t[17])); // Same root, same balance, 10 rotated left. + TEST_ASSERT(nullptr == cavlPrivateRetraceOnGrowth(&t[17])); // Same root, same balance, 10 rotated left. print(&t[30]); // Check 10 - ASSERT_TRUE(&t[15] == t[10].up); - ASSERT_TRUE(0 == t[10].bf); - ASSERT_TRUE(nullptr == t[10].lr[0]); - ASSERT_TRUE(nullptr == t[10].lr[1]); + TEST_ASSERT(&t[15] == t[10].up); + TEST_ASSERT(0 == t[10].bf); + TEST_ASSERT(nullptr == t[10].lr[0]); + TEST_ASSERT(nullptr == t[10].lr[1]); // Check 17 - ASSERT_TRUE(&t[15] == t[17].up); - ASSERT_TRUE(0 == t[17].bf); - ASSERT_TRUE(nullptr == t[17].lr[0]); - ASSERT_TRUE(nullptr == t[17].lr[1]); + TEST_ASSERT(&t[15] == t[17].up); + TEST_ASSERT(0 == t[17].bf); + TEST_ASSERT(nullptr == t[17].lr[0]); + TEST_ASSERT(nullptr == t[17].lr[1]); // Check 15 - ASSERT_TRUE(&t[20] == t[15].up); - ASSERT_TRUE(0 == t[15].bf); - ASSERT_TRUE(&t[10] == t[15].lr[0]); - ASSERT_TRUE(&t[17] == t[15].lr[1]); + TEST_ASSERT(&t[20] == t[15].up); + TEST_ASSERT(0 == t[15].bf); + TEST_ASSERT(&t[10] == t[15].lr[0]); + TEST_ASSERT(&t[17] == t[15].lr[1]); // Check 20 -- leaning left - ASSERT_TRUE(&t[30] == t[20].up); - ASSERT_TRUE(-1 == t[20].bf); - ASSERT_TRUE(&t[15] == t[20].lr[0]); - ASSERT_TRUE(&t[21] == t[20].lr[1]); + TEST_ASSERT(&t[30] == t[20].up); + TEST_ASSERT(-1 == t[20].bf); + TEST_ASSERT(&t[15] == t[20].lr[0]); + TEST_ASSERT(&t[21] == t[20].lr[1]); // Check the root -- still leaning left by one. - ASSERT_TRUE(nullptr == t[30].up); - ASSERT_TRUE(-1 == t[30].bf); - ASSERT_TRUE(&t[20] == t[30].lr[0]); - ASSERT_TRUE(&t[50] == t[30].lr[1]); + TEST_ASSERT(nullptr == t[30].up); + TEST_ASSERT(-1 == t[30].bf); + TEST_ASSERT(&t[20] == t[30].lr[0]); + TEST_ASSERT(&t[50] == t[30].lr[1]); // Check hard invariants. - ASSERT_TRUE(nullptr == findBrokenAncestry(&t[30])); - ASSERT_TRUE(nullptr == findBrokenBalanceFactor(&t[30])); - ASSERT_TRUE(9 == checkAscension(&t[30])); + TEST_ASSERT(nullptr == findBrokenAncestry(&t[30])); + TEST_ASSERT(nullptr == findBrokenBalanceFactor(&t[30])); + TEST_ASSERT(9 == checkAscension(&t[30])); std::puts("ADD 18:"); t[18] = {&t[17], {Zzzzzz, Zzzzzz}, 0}; t[17].lr[1] = &t[18]; - ASSERT_TRUE(nullptr == cavlPrivateRetraceOnGrowth(&t[18])); // Same root, 15 went left, 20 went right. + TEST_ASSERT(nullptr == cavlPrivateRetraceOnGrowth(&t[18])); // Same root, 15 went left, 20 went right. print(&t[30]); // Check 17 - ASSERT_TRUE(&t[30] == t[17].up); - ASSERT_TRUE(0 == t[17].bf); - ASSERT_TRUE(&t[15] == t[17].lr[0]); - ASSERT_TRUE(&t[20] == t[17].lr[1]); + TEST_ASSERT(&t[30] == t[17].up); + TEST_ASSERT(0 == t[17].bf); + TEST_ASSERT(&t[15] == t[17].lr[0]); + TEST_ASSERT(&t[20] == t[17].lr[1]); // Check 15 - ASSERT_TRUE(&t[17] == t[15].up); - ASSERT_TRUE(-1 == t[15].bf); - ASSERT_TRUE(&t[10] == t[15].lr[0]); - ASSERT_TRUE(nullptr == t[15].lr[1]); + TEST_ASSERT(&t[17] == t[15].up); + TEST_ASSERT(-1 == t[15].bf); + TEST_ASSERT(&t[10] == t[15].lr[0]); + TEST_ASSERT(nullptr == t[15].lr[1]); // Check 20 - ASSERT_TRUE(&t[17] == t[20].up); - ASSERT_TRUE(0 == t[20].bf); - ASSERT_TRUE(&t[18] == t[20].lr[0]); - ASSERT_TRUE(&t[21] == t[20].lr[1]); + TEST_ASSERT(&t[17] == t[20].up); + TEST_ASSERT(0 == t[20].bf); + TEST_ASSERT(&t[18] == t[20].lr[0]); + TEST_ASSERT(&t[21] == t[20].lr[1]); // Check 10 - ASSERT_TRUE(&t[15] == t[10].up); - ASSERT_TRUE(0 == t[10].bf); - ASSERT_TRUE(nullptr == t[10].lr[0]); - ASSERT_TRUE(nullptr == t[10].lr[1]); + TEST_ASSERT(&t[15] == t[10].up); + TEST_ASSERT(0 == t[10].bf); + TEST_ASSERT(nullptr == t[10].lr[0]); + TEST_ASSERT(nullptr == t[10].lr[1]); // Check 18 - ASSERT_TRUE(&t[20] == t[18].up); - ASSERT_TRUE(0 == t[18].bf); - ASSERT_TRUE(nullptr == t[18].lr[0]); - ASSERT_TRUE(nullptr == t[18].lr[1]); + TEST_ASSERT(&t[20] == t[18].up); + TEST_ASSERT(0 == t[18].bf); + TEST_ASSERT(nullptr == t[18].lr[0]); + TEST_ASSERT(nullptr == t[18].lr[1]); // Check 21 - ASSERT_TRUE(&t[20] == t[21].up); - ASSERT_TRUE(0 == t[21].bf); - ASSERT_TRUE(nullptr == t[21].lr[0]); - ASSERT_TRUE(nullptr == t[21].lr[1]); + TEST_ASSERT(&t[20] == t[21].up); + TEST_ASSERT(0 == t[21].bf); + TEST_ASSERT(nullptr == t[21].lr[0]); + TEST_ASSERT(nullptr == t[21].lr[1]); // Check hard invariants. - ASSERT_TRUE(nullptr == findBrokenAncestry(&t[30])); - ASSERT_TRUE(nullptr == findBrokenBalanceFactor(&t[30])); - ASSERT_TRUE(10 == checkAscension(&t[30])); + TEST_ASSERT(nullptr == findBrokenAncestry(&t[30])); + TEST_ASSERT(nullptr == findBrokenBalanceFactor(&t[30])); + TEST_ASSERT(10 == checkAscension(&t[30])); } -TEST(Cavl, SearchTrivial) +void testSearchTrivial() { using N = Node; // A @@ -656,29 +660,29 @@ TEST(Cavl, SearchTrivial) f = {&c, {Zz, Zz}, 0}; g = {&c, {Zz, Zz}, 0}; q = {Zz, {Zz, Zz}, 0}; - ASSERT_TRUE(nullptr == findBrokenBalanceFactor(&a)); - ASSERT_TRUE(nullptr == findBrokenAncestry(&a)); - ASSERT_TRUE(7 == checkAscension(&a)); + TEST_ASSERT(nullptr == findBrokenBalanceFactor(&a)); + TEST_ASSERT(nullptr == findBrokenAncestry(&a)); + TEST_ASSERT(7 == checkAscension(&a)); N* root = &a; - ASSERT_TRUE(nullptr == cavlSearch(reinterpret_cast(&root), nullptr, nullptr, nullptr)); // Bad arguments. - ASSERT_TRUE(&a == root); - ASSERT_TRUE(nullptr == search(&root, [&](const N& v) { return q.value - v.value; })); - ASSERT_TRUE(&a == root); - ASSERT_TRUE(&e == search(&root, [&](const N& v) { return e.value - v.value; })); - ASSERT_TRUE(&b == search(&root, [&](const N& v) { return b.value - v.value; })); - ASSERT_TRUE(&a == root); + TEST_ASSERT(nullptr == cavlSearch(reinterpret_cast(&root), nullptr, nullptr, nullptr)); // Bad arguments. + TEST_ASSERT(&a == root); + TEST_ASSERT(nullptr == search(&root, [&](const N& v) { return q.value - v.value; })); + TEST_ASSERT(&a == root); + TEST_ASSERT(&e == search(&root, [&](const N& v) { return e.value - v.value; })); + TEST_ASSERT(&b == search(&root, [&](const N& v) { return b.value - v.value; })); + TEST_ASSERT(&a == root); print(&a); - ASSERT_TRUE(nullptr == cavlFindExtremum(nullptr, true)); - ASSERT_TRUE(nullptr == cavlFindExtremum(nullptr, false)); - ASSERT_TRUE(&g == a.max()); - ASSERT_TRUE(&d == a.min()); - ASSERT_TRUE(&g == g.max()); - ASSERT_TRUE(&g == g.min()); - ASSERT_TRUE(&d == d.max()); - ASSERT_TRUE(&d == d.min()); + TEST_ASSERT(nullptr == cavlFindExtremum(nullptr, true)); + TEST_ASSERT(nullptr == cavlFindExtremum(nullptr, false)); + TEST_ASSERT(&g == a.max()); + TEST_ASSERT(&d == a.min()); + TEST_ASSERT(&g == g.max()); + TEST_ASSERT(&g == g.min()); + TEST_ASSERT(&d == d.max()); + TEST_ASSERT(&d == d.min()); } -TEST(Cavl, RemovalA) +void testRemovalA() { using N = Node; // 4 @@ -704,9 +708,9 @@ TEST(Cavl, RemovalA) t[9] = {&t[8], {Zzzzz, Zzzzz}, 00}; N* root = &t[4]; print(root); - ASSERT_TRUE(nullptr == findBrokenBalanceFactor(root)); - ASSERT_TRUE(nullptr == findBrokenAncestry(root)); - ASSERT_TRUE(9 == checkAscension(root)); + TEST_ASSERT(nullptr == findBrokenBalanceFactor(root)); + TEST_ASSERT(nullptr == findBrokenAncestry(root)); + TEST_ASSERT(9 == checkAscension(root)); // Remove 9, the easiest case. The rest of the tree remains unchanged. // 4 @@ -718,51 +722,51 @@ TEST(Cavl, RemovalA) // 7 std::puts("REMOVE 9:"); remove(&root, &t[9]); - ASSERT_TRUE(&t[4] == root); + TEST_ASSERT(&t[4] == root); print(root); - ASSERT_TRUE(nullptr == findBrokenBalanceFactor(root)); - ASSERT_TRUE(nullptr == findBrokenAncestry(root)); - ASSERT_TRUE(8 == checkAscension(root)); + TEST_ASSERT(nullptr == findBrokenBalanceFactor(root)); + TEST_ASSERT(nullptr == findBrokenAncestry(root)); + TEST_ASSERT(8 == checkAscension(root)); // 1 - ASSERT_TRUE(&t[2] == t[1].up); - ASSERT_TRUE(Zzzzz == t[1].lr[0]); - ASSERT_TRUE(Zzzzz == t[1].lr[1]); - ASSERT_TRUE(00 == t[1].bf); + TEST_ASSERT(&t[2] == t[1].up); + TEST_ASSERT(Zzzzz == t[1].lr[0]); + TEST_ASSERT(Zzzzz == t[1].lr[1]); + TEST_ASSERT(00 == t[1].bf); // 2 - ASSERT_TRUE(&t[4] == t[2].up); - ASSERT_TRUE(&t[1] == t[2].lr[0]); - ASSERT_TRUE(&t[3] == t[2].lr[1]); - ASSERT_TRUE(00 == t[2].bf); + TEST_ASSERT(&t[4] == t[2].up); + TEST_ASSERT(&t[1] == t[2].lr[0]); + TEST_ASSERT(&t[3] == t[2].lr[1]); + TEST_ASSERT(00 == t[2].bf); // 3 - ASSERT_TRUE(&t[2] == t[3].up); - ASSERT_TRUE(Zzzzz == t[3].lr[0]); - ASSERT_TRUE(Zzzzz == t[3].lr[1]); - ASSERT_TRUE(00 == t[3].bf); + TEST_ASSERT(&t[2] == t[3].up); + TEST_ASSERT(Zzzzz == t[3].lr[0]); + TEST_ASSERT(Zzzzz == t[3].lr[1]); + TEST_ASSERT(00 == t[3].bf); // 4 - ASSERT_TRUE(Zzzzz == t[4].up); // Nihil Supernum - ASSERT_TRUE(&t[2] == t[4].lr[0]); - ASSERT_TRUE(&t[6] == t[4].lr[1]); - ASSERT_TRUE(+1 == t[4].bf); + TEST_ASSERT(Zzzzz == t[4].up); // Nihil Supernum + TEST_ASSERT(&t[2] == t[4].lr[0]); + TEST_ASSERT(&t[6] == t[4].lr[1]); + TEST_ASSERT(+1 == t[4].bf); // 5 - ASSERT_TRUE(&t[6] == t[5].up); - ASSERT_TRUE(Zzzzz == t[5].lr[0]); - ASSERT_TRUE(Zzzzz == t[5].lr[1]); - ASSERT_TRUE(00 == t[5].bf); + TEST_ASSERT(&t[6] == t[5].up); + TEST_ASSERT(Zzzzz == t[5].lr[0]); + TEST_ASSERT(Zzzzz == t[5].lr[1]); + TEST_ASSERT(00 == t[5].bf); // 6 - ASSERT_TRUE(&t[4] == t[6].up); - ASSERT_TRUE(&t[5] == t[6].lr[0]); - ASSERT_TRUE(&t[8] == t[6].lr[1]); - ASSERT_TRUE(+1 == t[6].bf); + TEST_ASSERT(&t[4] == t[6].up); + TEST_ASSERT(&t[5] == t[6].lr[0]); + TEST_ASSERT(&t[8] == t[6].lr[1]); + TEST_ASSERT(+1 == t[6].bf); // 7 - ASSERT_TRUE(&t[8] == t[7].up); - ASSERT_TRUE(Zzzzz == t[7].lr[0]); - ASSERT_TRUE(Zzzzz == t[7].lr[1]); - ASSERT_TRUE(00 == t[7].bf); + TEST_ASSERT(&t[8] == t[7].up); + TEST_ASSERT(Zzzzz == t[7].lr[0]); + TEST_ASSERT(Zzzzz == t[7].lr[1]); + TEST_ASSERT(00 == t[7].bf); // 8 - ASSERT_TRUE(&t[6] == t[8].up); - ASSERT_TRUE(&t[7] == t[8].lr[0]); - ASSERT_TRUE(Zzzzz == t[8].lr[1]); - ASSERT_TRUE(-1 == t[8].bf); + TEST_ASSERT(&t[6] == t[8].up); + TEST_ASSERT(&t[7] == t[8].lr[0]); + TEST_ASSERT(Zzzzz == t[8].lr[1]); + TEST_ASSERT(-1 == t[8].bf); // Remove 8, 7 takes its place (the one-child case). The rest of the tree remains unchanged. // 4 @@ -772,46 +776,46 @@ TEST(Cavl, RemovalA) // 1 3 5 7 std::puts("REMOVE 8:"); remove(&root, &t[8]); - ASSERT_TRUE(&t[4] == root); + TEST_ASSERT(&t[4] == root); print(root); - ASSERT_TRUE(nullptr == findBrokenBalanceFactor(root)); - ASSERT_TRUE(nullptr == findBrokenAncestry(root)); - ASSERT_TRUE(7 == checkAscension(root)); + TEST_ASSERT(nullptr == findBrokenBalanceFactor(root)); + TEST_ASSERT(nullptr == findBrokenAncestry(root)); + TEST_ASSERT(7 == checkAscension(root)); // 1 - ASSERT_TRUE(&t[2] == t[1].up); - ASSERT_TRUE(Zzzzz == t[1].lr[0]); - ASSERT_TRUE(Zzzzz == t[1].lr[1]); - ASSERT_TRUE(00 == t[1].bf); + TEST_ASSERT(&t[2] == t[1].up); + TEST_ASSERT(Zzzzz == t[1].lr[0]); + TEST_ASSERT(Zzzzz == t[1].lr[1]); + TEST_ASSERT(00 == t[1].bf); // 2 - ASSERT_TRUE(&t[4] == t[2].up); - ASSERT_TRUE(&t[1] == t[2].lr[0]); - ASSERT_TRUE(&t[3] == t[2].lr[1]); - ASSERT_TRUE(00 == t[2].bf); + TEST_ASSERT(&t[4] == t[2].up); + TEST_ASSERT(&t[1] == t[2].lr[0]); + TEST_ASSERT(&t[3] == t[2].lr[1]); + TEST_ASSERT(00 == t[2].bf); // 3 - ASSERT_TRUE(&t[2] == t[3].up); - ASSERT_TRUE(Zzzzz == t[3].lr[0]); - ASSERT_TRUE(Zzzzz == t[3].lr[1]); - ASSERT_TRUE(00 == t[3].bf); + TEST_ASSERT(&t[2] == t[3].up); + TEST_ASSERT(Zzzzz == t[3].lr[0]); + TEST_ASSERT(Zzzzz == t[3].lr[1]); + TEST_ASSERT(00 == t[3].bf); // 4 - ASSERT_TRUE(Zzzzz == t[4].up); // Nihil Supernum - ASSERT_TRUE(&t[2] == t[4].lr[0]); - ASSERT_TRUE(&t[6] == t[4].lr[1]); - ASSERT_TRUE(00 == t[4].bf); + TEST_ASSERT(Zzzzz == t[4].up); // Nihil Supernum + TEST_ASSERT(&t[2] == t[4].lr[0]); + TEST_ASSERT(&t[6] == t[4].lr[1]); + TEST_ASSERT(00 == t[4].bf); // 5 - ASSERT_TRUE(&t[6] == t[5].up); - ASSERT_TRUE(Zzzzz == t[5].lr[0]); - ASSERT_TRUE(Zzzzz == t[5].lr[1]); - ASSERT_TRUE(00 == t[5].bf); + TEST_ASSERT(&t[6] == t[5].up); + TEST_ASSERT(Zzzzz == t[5].lr[0]); + TEST_ASSERT(Zzzzz == t[5].lr[1]); + TEST_ASSERT(00 == t[5].bf); // 6 - ASSERT_TRUE(&t[4] == t[6].up); - ASSERT_TRUE(&t[5] == t[6].lr[0]); - ASSERT_TRUE(&t[7] == t[6].lr[1]); - ASSERT_TRUE(00 == t[6].bf); + TEST_ASSERT(&t[4] == t[6].up); + TEST_ASSERT(&t[5] == t[6].lr[0]); + TEST_ASSERT(&t[7] == t[6].lr[1]); + TEST_ASSERT(00 == t[6].bf); // 7 - ASSERT_TRUE(&t[6] == t[7].up); - ASSERT_TRUE(Zzzzz == t[7].lr[0]); - ASSERT_TRUE(Zzzzz == t[7].lr[1]); - ASSERT_TRUE(00 == t[7].bf); + TEST_ASSERT(&t[6] == t[7].up); + TEST_ASSERT(Zzzzz == t[7].lr[0]); + TEST_ASSERT(Zzzzz == t[7].lr[1]); + TEST_ASSERT(00 == t[7].bf); // Remove the root node 4, 5 takes its place. The overall structure remains unchanged except that 5 is now the root. // 5 @@ -822,40 +826,40 @@ TEST(Cavl, RemovalA) std::puts("REMOVE 4:"); remove(&root, &t[4]); print(root); - ASSERT_TRUE(&t[5] == root); - ASSERT_TRUE(nullptr == findBrokenBalanceFactor(root)); - ASSERT_TRUE(nullptr == findBrokenAncestry(root)); - ASSERT_TRUE(6 == checkAscension(root)); + TEST_ASSERT(&t[5] == root); + TEST_ASSERT(nullptr == findBrokenBalanceFactor(root)); + TEST_ASSERT(nullptr == findBrokenAncestry(root)); + TEST_ASSERT(6 == checkAscension(root)); // 1 - ASSERT_TRUE(&t[2] == t[1].up); - ASSERT_TRUE(Zzzzz == t[1].lr[0]); - ASSERT_TRUE(Zzzzz == t[1].lr[1]); - ASSERT_TRUE(00 == t[1].bf); + TEST_ASSERT(&t[2] == t[1].up); + TEST_ASSERT(Zzzzz == t[1].lr[0]); + TEST_ASSERT(Zzzzz == t[1].lr[1]); + TEST_ASSERT(00 == t[1].bf); // 2 - ASSERT_TRUE(&t[5] == t[2].up); - ASSERT_TRUE(&t[1] == t[2].lr[0]); - ASSERT_TRUE(&t[3] == t[2].lr[1]); - ASSERT_TRUE(00 == t[2].bf); + TEST_ASSERT(&t[5] == t[2].up); + TEST_ASSERT(&t[1] == t[2].lr[0]); + TEST_ASSERT(&t[3] == t[2].lr[1]); + TEST_ASSERT(00 == t[2].bf); // 3 - ASSERT_TRUE(&t[2] == t[3].up); - ASSERT_TRUE(Zzzzz == t[3].lr[0]); - ASSERT_TRUE(Zzzzz == t[3].lr[1]); - ASSERT_TRUE(00 == t[3].bf); + TEST_ASSERT(&t[2] == t[3].up); + TEST_ASSERT(Zzzzz == t[3].lr[0]); + TEST_ASSERT(Zzzzz == t[3].lr[1]); + TEST_ASSERT(00 == t[3].bf); // 5 - ASSERT_TRUE(Zzzzz == t[5].up); // Nihil Supernum - ASSERT_TRUE(&t[2] == t[5].lr[0]); - ASSERT_TRUE(&t[6] == t[5].lr[1]); - ASSERT_TRUE(00 == t[5].bf); + TEST_ASSERT(Zzzzz == t[5].up); // Nihil Supernum + TEST_ASSERT(&t[2] == t[5].lr[0]); + TEST_ASSERT(&t[6] == t[5].lr[1]); + TEST_ASSERT(00 == t[5].bf); // 6 - ASSERT_TRUE(&t[5] == t[6].up); - ASSERT_TRUE(Zzzzz == t[6].lr[0]); - ASSERT_TRUE(&t[7] == t[6].lr[1]); - ASSERT_TRUE(+1 == t[6].bf); + TEST_ASSERT(&t[5] == t[6].up); + TEST_ASSERT(Zzzzz == t[6].lr[0]); + TEST_ASSERT(&t[7] == t[6].lr[1]); + TEST_ASSERT(+1 == t[6].bf); // 7 - ASSERT_TRUE(&t[6] == t[7].up); - ASSERT_TRUE(Zzzzz == t[7].lr[0]); - ASSERT_TRUE(Zzzzz == t[7].lr[1]); - ASSERT_TRUE(00 == t[7].bf); + TEST_ASSERT(&t[6] == t[7].up); + TEST_ASSERT(Zzzzz == t[7].lr[0]); + TEST_ASSERT(Zzzzz == t[7].lr[1]); + TEST_ASSERT(00 == t[7].bf); // Remove the root node 5, 6 takes its place. // 6 @@ -865,36 +869,36 @@ TEST(Cavl, RemovalA) // 1 3 std::puts("REMOVE 5:"); remove(&root, &t[5]); - ASSERT_TRUE(&t[6] == root); + TEST_ASSERT(&t[6] == root); print(root); - ASSERT_TRUE(nullptr == findBrokenBalanceFactor(root)); - ASSERT_TRUE(nullptr == findBrokenAncestry(root)); - ASSERT_TRUE(5 == checkAscension(root)); + TEST_ASSERT(nullptr == findBrokenBalanceFactor(root)); + TEST_ASSERT(nullptr == findBrokenAncestry(root)); + TEST_ASSERT(5 == checkAscension(root)); // 1 - ASSERT_TRUE(&t[2] == t[1].up); - ASSERT_TRUE(Zzzzz == t[1].lr[0]); - ASSERT_TRUE(Zzzzz == t[1].lr[1]); - ASSERT_TRUE(00 == t[1].bf); + TEST_ASSERT(&t[2] == t[1].up); + TEST_ASSERT(Zzzzz == t[1].lr[0]); + TEST_ASSERT(Zzzzz == t[1].lr[1]); + TEST_ASSERT(00 == t[1].bf); // 2 - ASSERT_TRUE(&t[6] == t[2].up); - ASSERT_TRUE(&t[1] == t[2].lr[0]); - ASSERT_TRUE(&t[3] == t[2].lr[1]); - ASSERT_TRUE(00 == t[2].bf); + TEST_ASSERT(&t[6] == t[2].up); + TEST_ASSERT(&t[1] == t[2].lr[0]); + TEST_ASSERT(&t[3] == t[2].lr[1]); + TEST_ASSERT(00 == t[2].bf); // 3 - ASSERT_TRUE(&t[2] == t[3].up); - ASSERT_TRUE(Zzzzz == t[3].lr[0]); - ASSERT_TRUE(Zzzzz == t[3].lr[1]); - ASSERT_TRUE(00 == t[3].bf); + TEST_ASSERT(&t[2] == t[3].up); + TEST_ASSERT(Zzzzz == t[3].lr[0]); + TEST_ASSERT(Zzzzz == t[3].lr[1]); + TEST_ASSERT(00 == t[3].bf); // 6 - ASSERT_TRUE(Zzzzz == t[6].up); // Nihil Supernum - ASSERT_TRUE(&t[2] == t[6].lr[0]); - ASSERT_TRUE(&t[7] == t[6].lr[1]); - ASSERT_TRUE(-1 == t[6].bf); + TEST_ASSERT(Zzzzz == t[6].up); // Nihil Supernum + TEST_ASSERT(&t[2] == t[6].lr[0]); + TEST_ASSERT(&t[7] == t[6].lr[1]); + TEST_ASSERT(-1 == t[6].bf); // 7 - ASSERT_TRUE(&t[6] == t[7].up); - ASSERT_TRUE(Zzzzz == t[7].lr[0]); - ASSERT_TRUE(Zzzzz == t[7].lr[1]); - ASSERT_TRUE(00 == t[7].bf); + TEST_ASSERT(&t[6] == t[7].up); + TEST_ASSERT(Zzzzz == t[7].lr[0]); + TEST_ASSERT(Zzzzz == t[7].lr[1]); + TEST_ASSERT(00 == t[7].bf); // Remove the root node 6, 7 takes its place, then right rotation is done to restore balance, 2 is the new root. // 2 @@ -904,31 +908,31 @@ TEST(Cavl, RemovalA) // 3 std::puts("REMOVE 6:"); remove(&root, &t[6]); - ASSERT_TRUE(&t[2] == root); + TEST_ASSERT(&t[2] == root); print(root); - ASSERT_TRUE(nullptr == findBrokenBalanceFactor(root)); - ASSERT_TRUE(nullptr == findBrokenAncestry(root)); - ASSERT_TRUE(4 == checkAscension(root)); + TEST_ASSERT(nullptr == findBrokenBalanceFactor(root)); + TEST_ASSERT(nullptr == findBrokenAncestry(root)); + TEST_ASSERT(4 == checkAscension(root)); // 1 - ASSERT_TRUE(&t[2] == t[1].up); - ASSERT_TRUE(Zzzzz == t[1].lr[0]); - ASSERT_TRUE(Zzzzz == t[1].lr[1]); - ASSERT_TRUE(00 == t[1].bf); + TEST_ASSERT(&t[2] == t[1].up); + TEST_ASSERT(Zzzzz == t[1].lr[0]); + TEST_ASSERT(Zzzzz == t[1].lr[1]); + TEST_ASSERT(00 == t[1].bf); // 2 - ASSERT_TRUE(Zzzzz == t[2].up); // Nihil Supernum - ASSERT_TRUE(&t[1] == t[2].lr[0]); - ASSERT_TRUE(&t[7] == t[2].lr[1]); - ASSERT_TRUE(+1 == t[2].bf); + TEST_ASSERT(Zzzzz == t[2].up); // Nihil Supernum + TEST_ASSERT(&t[1] == t[2].lr[0]); + TEST_ASSERT(&t[7] == t[2].lr[1]); + TEST_ASSERT(+1 == t[2].bf); // 3 - ASSERT_TRUE(&t[7] == t[3].up); - ASSERT_TRUE(Zzzzz == t[3].lr[0]); - ASSERT_TRUE(Zzzzz == t[3].lr[1]); - ASSERT_TRUE(00 == t[3].bf); + TEST_ASSERT(&t[7] == t[3].up); + TEST_ASSERT(Zzzzz == t[3].lr[0]); + TEST_ASSERT(Zzzzz == t[3].lr[1]); + TEST_ASSERT(00 == t[3].bf); // 7 - ASSERT_TRUE(&t[2] == t[7].up); - ASSERT_TRUE(&t[3] == t[7].lr[0]); - ASSERT_TRUE(Zzzzz == t[7].lr[1]); - ASSERT_TRUE(-1 == t[7].bf); + TEST_ASSERT(&t[2] == t[7].up); + TEST_ASSERT(&t[3] == t[7].lr[0]); + TEST_ASSERT(Zzzzz == t[7].lr[1]); + TEST_ASSERT(-1 == t[7].bf); // Remove 1, then balancing makes 3 the new root node. // 3 @@ -936,26 +940,26 @@ TEST(Cavl, RemovalA) // 2 7 std::puts("REMOVE 1:"); remove(&root, &t[1]); - ASSERT_TRUE(&t[3] == root); + TEST_ASSERT(&t[3] == root); print(root); - ASSERT_TRUE(nullptr == findBrokenBalanceFactor(root)); - ASSERT_TRUE(nullptr == findBrokenAncestry(root)); - ASSERT_TRUE(3 == checkAscension(root)); + TEST_ASSERT(nullptr == findBrokenBalanceFactor(root)); + TEST_ASSERT(nullptr == findBrokenAncestry(root)); + TEST_ASSERT(3 == checkAscension(root)); // 2 - ASSERT_TRUE(&t[3] == t[2].up); - ASSERT_TRUE(Zzzzz == t[2].lr[0]); - ASSERT_TRUE(Zzzzz == t[2].lr[1]); - ASSERT_TRUE(0 == t[2].bf); + TEST_ASSERT(&t[3] == t[2].up); + TEST_ASSERT(Zzzzz == t[2].lr[0]); + TEST_ASSERT(Zzzzz == t[2].lr[1]); + TEST_ASSERT(0 == t[2].bf); // 3 - ASSERT_TRUE(Zzzzz == t[3].up); // Nihil Supernum - ASSERT_TRUE(&t[2] == t[3].lr[0]); - ASSERT_TRUE(&t[7] == t[3].lr[1]); - ASSERT_TRUE(00 == t[3].bf); + TEST_ASSERT(Zzzzz == t[3].up); // Nihil Supernum + TEST_ASSERT(&t[2] == t[3].lr[0]); + TEST_ASSERT(&t[7] == t[3].lr[1]); + TEST_ASSERT(00 == t[3].bf); // 7 - ASSERT_TRUE(&t[3] == t[7].up); - ASSERT_TRUE(Zzzzz == t[7].lr[0]); - ASSERT_TRUE(Zzzzz == t[7].lr[1]); - ASSERT_TRUE(00 == t[7].bf); + TEST_ASSERT(&t[3] == t[7].up); + TEST_ASSERT(Zzzzz == t[7].lr[0]); + TEST_ASSERT(Zzzzz == t[7].lr[1]); + TEST_ASSERT(00 == t[7].bf); // Remove 7. // 3 @@ -963,43 +967,43 @@ TEST(Cavl, RemovalA) // 2 std::puts("REMOVE 7:"); remove(&root, &t[7]); - ASSERT_TRUE(&t[3] == root); + TEST_ASSERT(&t[3] == root); print(root); - ASSERT_TRUE(nullptr == findBrokenBalanceFactor(root)); - ASSERT_TRUE(nullptr == findBrokenAncestry(root)); - ASSERT_TRUE(2 == checkAscension(root)); + TEST_ASSERT(nullptr == findBrokenBalanceFactor(root)); + TEST_ASSERT(nullptr == findBrokenAncestry(root)); + TEST_ASSERT(2 == checkAscension(root)); // 2 - ASSERT_TRUE(&t[3] == t[2].up); - ASSERT_TRUE(Zzzzz == t[2].lr[0]); - ASSERT_TRUE(Zzzzz == t[2].lr[1]); - ASSERT_TRUE(0 == t[2].bf); + TEST_ASSERT(&t[3] == t[2].up); + TEST_ASSERT(Zzzzz == t[2].lr[0]); + TEST_ASSERT(Zzzzz == t[2].lr[1]); + TEST_ASSERT(0 == t[2].bf); // 3 - ASSERT_TRUE(Zzzzz == t[3].up); // Nihil Supernum - ASSERT_TRUE(&t[2] == t[3].lr[0]); - ASSERT_TRUE(Zzzzz == t[3].lr[1]); - ASSERT_TRUE(-1 == t[3].bf); + TEST_ASSERT(Zzzzz == t[3].up); // Nihil Supernum + TEST_ASSERT(&t[2] == t[3].lr[0]); + TEST_ASSERT(Zzzzz == t[3].lr[1]); + TEST_ASSERT(-1 == t[3].bf); // Remove 3. Only 2 is left, which is now obviously the root. std::puts("REMOVE 3:"); remove(&root, &t[3]); - ASSERT_TRUE(&t[2] == root); + TEST_ASSERT(&t[2] == root); print(root); - ASSERT_TRUE(nullptr == findBrokenBalanceFactor(root)); - ASSERT_TRUE(nullptr == findBrokenAncestry(root)); - ASSERT_TRUE(1 == checkAscension(root)); + TEST_ASSERT(nullptr == findBrokenBalanceFactor(root)); + TEST_ASSERT(nullptr == findBrokenAncestry(root)); + TEST_ASSERT(1 == checkAscension(root)); // 2 - ASSERT_TRUE(Zzzzz == t[2].up); - ASSERT_TRUE(Zzzzz == t[2].lr[0]); - ASSERT_TRUE(Zzzzz == t[2].lr[1]); - ASSERT_TRUE(0 == t[2].bf); + TEST_ASSERT(Zzzzz == t[2].up); + TEST_ASSERT(Zzzzz == t[2].lr[0]); + TEST_ASSERT(Zzzzz == t[2].lr[1]); + TEST_ASSERT(0 == t[2].bf); // Remove 2. The tree is now empty, make sure the root pointer is updated accordingly. std::puts("REMOVE 2:"); remove(&root, &t[2]); - ASSERT_TRUE(nullptr == root); + TEST_ASSERT(nullptr == root); } -TEST(Cavl, MutationManual) +void testMutationManual() { using N = Node; // Build a tree with 31 elements from 1 to 31 inclusive by adding new elements successively: @@ -1022,27 +1026,27 @@ TEST(Cavl, MutationManual) for (std::uint8_t i = 1; i < 32; i++) { const auto pred = [&](const N& v) { return t.at(i).value - v.value; }; - ASSERT_TRUE(nullptr == search(&root, pred)); - ASSERT_TRUE(&t[i] == search(&root, pred, [&]() { return &t.at(i); })); - ASSERT_TRUE(&t[i] == search(&root, pred)); + TEST_ASSERT(nullptr == search(&root, pred)); + TEST_ASSERT(&t[i] == search(&root, pred, [&]() { return &t.at(i); })); + TEST_ASSERT(&t[i] == search(&root, pred)); // Validate the tree after every mutation. - ASSERT_TRUE(nullptr != root); - ASSERT_TRUE(nullptr == findBrokenBalanceFactor(root)); - ASSERT_TRUE(nullptr == findBrokenAncestry(root)); - ASSERT_TRUE(i == checkAscension(root)); + TEST_ASSERT(nullptr != root); + TEST_ASSERT(nullptr == findBrokenBalanceFactor(root)); + TEST_ASSERT(nullptr == findBrokenAncestry(root)); + TEST_ASSERT(i == checkAscension(root)); } print(root); - ASSERT_TRUE(nullptr == findBrokenBalanceFactor(root)); - ASSERT_TRUE(nullptr == findBrokenAncestry(root)); - ASSERT_TRUE(31 == checkAscension(root)); + TEST_ASSERT(nullptr == findBrokenBalanceFactor(root)); + TEST_ASSERT(nullptr == findBrokenAncestry(root)); + TEST_ASSERT(31 == checkAscension(root)); // Check composition -- ensure that every element is in the tree and it is there exactly once. { std::array seen{}; traverse(root, [&](const N* const n) { - ASSERT_TRUE(!seen.at(n->value)); + TEST_ASSERT(!seen.at(n->value)); seen[n->value] = true; }); - ASSERT_TRUE(std::all_of(&seen[1], &seen[31], [](bool x) { return x; })); + TEST_ASSERT(std::all_of(&seen[1], &seen[31], [](bool x) { return x; })); } // REMOVE 24 @@ -1056,15 +1060,15 @@ TEST(Cavl, MutationManual) // / ` / ` / ` / ` / ` / ` ` / ` // 1 3 5 7 9 11 13 15 17 19 21 23 27 29 31 std::puts("REMOVE 24:"); - ASSERT_TRUE(t[24].checkLinkageUpLeftRightBF(&t[16], &t[20], &t[28], 00)); + TEST_ASSERT(t[24].checkLinkageUpLeftRightBF(&t[16], &t[20], &t[28], 00)); remove(&root, &t[24]); - ASSERT_TRUE(&t[16] == root); + TEST_ASSERT(&t[16] == root); print(root); - ASSERT_TRUE(t[25].checkLinkageUpLeftRightBF(&t[16], &t[20], &t[28], 00)); - ASSERT_TRUE(t[26].checkLinkageUpLeftRightBF(&t[28], Zzzzzz, &t[27], +1)); - ASSERT_TRUE(nullptr == findBrokenBalanceFactor(root)); - ASSERT_TRUE(nullptr == findBrokenAncestry(root)); - ASSERT_TRUE(30 == checkAscension(root)); + TEST_ASSERT(t[25].checkLinkageUpLeftRightBF(&t[16], &t[20], &t[28], 00)); + TEST_ASSERT(t[26].checkLinkageUpLeftRightBF(&t[28], Zzzzzz, &t[27], +1)); + TEST_ASSERT(nullptr == findBrokenBalanceFactor(root)); + TEST_ASSERT(nullptr == findBrokenAncestry(root)); + TEST_ASSERT(30 == checkAscension(root)); // REMOVE 25 // 16 @@ -1077,15 +1081,15 @@ TEST(Cavl, MutationManual) // / ` / ` / ` / ` / ` / ` / ` // 1 3 5 7 9 11 13 15 17 19 21 23 29 31 std::puts("REMOVE 25:"); - ASSERT_TRUE(t[25].checkLinkageUpLeftRightBF(&t[16], &t[20], &t[28], 00)); + TEST_ASSERT(t[25].checkLinkageUpLeftRightBF(&t[16], &t[20], &t[28], 00)); remove(&root, &t[25]); - ASSERT_TRUE(&t[16] == root); + TEST_ASSERT(&t[16] == root); print(root); - ASSERT_TRUE(t[26].checkLinkageUpLeftRightBF(&t[16], &t[20], &t[28], 00)); - ASSERT_TRUE(t[28].checkLinkageUpLeftRightBF(&t[26], &t[27], &t[30], +1)); - ASSERT_TRUE(nullptr == findBrokenBalanceFactor(root)); - ASSERT_TRUE(nullptr == findBrokenAncestry(root)); - ASSERT_TRUE(29 == checkAscension(root)); + TEST_ASSERT(t[26].checkLinkageUpLeftRightBF(&t[16], &t[20], &t[28], 00)); + TEST_ASSERT(t[28].checkLinkageUpLeftRightBF(&t[26], &t[27], &t[30], +1)); + TEST_ASSERT(nullptr == findBrokenBalanceFactor(root)); + TEST_ASSERT(nullptr == findBrokenAncestry(root)); + TEST_ASSERT(29 == checkAscension(root)); // REMOVE 26 // 16 @@ -1098,16 +1102,16 @@ TEST(Cavl, MutationManual) // / ` / ` / ` / ` / ` / ` ` // 1 3 5 7 9 11 13 15 17 19 21 23 29 std::puts("REMOVE 26:"); - ASSERT_TRUE(t[26].checkLinkageUpLeftRightBF(&t[16], &t[20], &t[28], 00)); + TEST_ASSERT(t[26].checkLinkageUpLeftRightBF(&t[16], &t[20], &t[28], 00)); remove(&root, &t[26]); - ASSERT_TRUE(&t[16] == root); + TEST_ASSERT(&t[16] == root); print(root); - ASSERT_TRUE(t[27].checkLinkageUpLeftRightBF(&t[16], &t[20], &t[30], 00)); - ASSERT_TRUE(t[30].checkLinkageUpLeftRightBF(&t[27], &t[28], &t[31], -1)); - ASSERT_TRUE(t[28].checkLinkageUpLeftRightBF(&t[30], Zzzzzz, &t[29], +1)); - ASSERT_TRUE(nullptr == findBrokenBalanceFactor(root)); - ASSERT_TRUE(nullptr == findBrokenAncestry(root)); - ASSERT_TRUE(28 == checkAscension(root)); + TEST_ASSERT(t[27].checkLinkageUpLeftRightBF(&t[16], &t[20], &t[30], 00)); + TEST_ASSERT(t[30].checkLinkageUpLeftRightBF(&t[27], &t[28], &t[31], -1)); + TEST_ASSERT(t[28].checkLinkageUpLeftRightBF(&t[30], Zzzzzz, &t[29], +1)); + TEST_ASSERT(nullptr == findBrokenBalanceFactor(root)); + TEST_ASSERT(nullptr == findBrokenAncestry(root)); + TEST_ASSERT(28 == checkAscension(root)); // REMOVE 20 // 16 @@ -1120,15 +1124,15 @@ TEST(Cavl, MutationManual) // / ` / ` / ` / ` / ` ` ` // 1 3 5 7 9 11 13 15 17 19 23 29 std::puts("REMOVE 20:"); - ASSERT_TRUE(t[20].checkLinkageUpLeftRightBF(&t[27], &t[18], &t[22], 00)); + TEST_ASSERT(t[20].checkLinkageUpLeftRightBF(&t[27], &t[18], &t[22], 00)); remove(&root, &t[20]); - ASSERT_TRUE(&t[16] == root); + TEST_ASSERT(&t[16] == root); print(root); - ASSERT_TRUE(t[21].checkLinkageUpLeftRightBF(&t[27], &t[18], &t[22], 00)); - ASSERT_TRUE(t[22].checkLinkageUpLeftRightBF(&t[21], Zzzzzz, &t[23], +1)); - ASSERT_TRUE(nullptr == findBrokenBalanceFactor(root)); - ASSERT_TRUE(nullptr == findBrokenAncestry(root)); - ASSERT_TRUE(27 == checkAscension(root)); + TEST_ASSERT(t[21].checkLinkageUpLeftRightBF(&t[27], &t[18], &t[22], 00)); + TEST_ASSERT(t[22].checkLinkageUpLeftRightBF(&t[21], Zzzzzz, &t[23], +1)); + TEST_ASSERT(nullptr == findBrokenBalanceFactor(root)); + TEST_ASSERT(nullptr == findBrokenAncestry(root)); + TEST_ASSERT(27 == checkAscension(root)); // REMOVE 27 // 16 @@ -1141,15 +1145,15 @@ TEST(Cavl, MutationManual) // / ` / ` / ` / ` / ` ` // 1 3 5 7 9 11 13 15 17 19 23 std::puts("REMOVE 27:"); - ASSERT_TRUE(t[27].checkLinkageUpLeftRightBF(&t[16], &t[21], &t[30], 00)); + TEST_ASSERT(t[27].checkLinkageUpLeftRightBF(&t[16], &t[21], &t[30], 00)); remove(&root, &t[27]); - ASSERT_TRUE(&t[16] == root); + TEST_ASSERT(&t[16] == root); print(root); - ASSERT_TRUE(t[28].checkLinkageUpLeftRightBF(&t[16], &t[21], &t[30], -1)); - ASSERT_TRUE(t[30].checkLinkageUpLeftRightBF(&t[28], &t[29], &t[31], 00)); - ASSERT_TRUE(nullptr == findBrokenBalanceFactor(root)); - ASSERT_TRUE(nullptr == findBrokenAncestry(root)); - ASSERT_TRUE(26 == checkAscension(root)); + TEST_ASSERT(t[28].checkLinkageUpLeftRightBF(&t[16], &t[21], &t[30], -1)); + TEST_ASSERT(t[30].checkLinkageUpLeftRightBF(&t[28], &t[29], &t[31], 00)); + TEST_ASSERT(nullptr == findBrokenBalanceFactor(root)); + TEST_ASSERT(nullptr == findBrokenAncestry(root)); + TEST_ASSERT(26 == checkAscension(root)); // REMOVE 28 // 16 @@ -1162,15 +1166,15 @@ TEST(Cavl, MutationManual) // / ` / ` / ` / ` / ` ` // 1 3 5 7 9 11 13 15 17 19 23 std::puts("REMOVE 28:"); - ASSERT_TRUE(t[28].checkLinkageUpLeftRightBF(&t[16], &t[21], &t[30], -1)); + TEST_ASSERT(t[28].checkLinkageUpLeftRightBF(&t[16], &t[21], &t[30], -1)); remove(&root, &t[28]); - ASSERT_TRUE(&t[16] == root); + TEST_ASSERT(&t[16] == root); print(root); - ASSERT_TRUE(t[29].checkLinkageUpLeftRightBF(&t[16], &t[21], &t[30], -1)); - ASSERT_TRUE(t[30].checkLinkageUpLeftRightBF(&t[29], Zzzzzz, &t[31], +1)); - ASSERT_TRUE(nullptr == findBrokenBalanceFactor(root)); - ASSERT_TRUE(nullptr == findBrokenAncestry(root)); - ASSERT_TRUE(25 == checkAscension(root)); + TEST_ASSERT(t[29].checkLinkageUpLeftRightBF(&t[16], &t[21], &t[30], -1)); + TEST_ASSERT(t[30].checkLinkageUpLeftRightBF(&t[29], Zzzzzz, &t[31], +1)); + TEST_ASSERT(nullptr == findBrokenBalanceFactor(root)); + TEST_ASSERT(nullptr == findBrokenAncestry(root)); + TEST_ASSERT(25 == checkAscension(root)); // REMOVE 29; UNBALANCED TREE BEFORE ROTATION: // 16 @@ -1194,18 +1198,18 @@ TEST(Cavl, MutationManual) // / ` / ` / ` / ` ` // 1 3 5 7 9 11 13 15 23 std::puts("REMOVE 29:"); - ASSERT_TRUE(t[29].checkLinkageUpLeftRightBF(&t[16], &t[21], &t[30], -1)); + TEST_ASSERT(t[29].checkLinkageUpLeftRightBF(&t[16], &t[21], &t[30], -1)); remove(&root, &t[29]); - ASSERT_TRUE(&t[16] == root); + TEST_ASSERT(&t[16] == root); print(root); - ASSERT_TRUE(t[21].checkLinkageUpLeftRightBF(&t[16], &t[18], &t[30], +1)); - ASSERT_TRUE(t[18].checkLinkageUpLeftRightBF(&t[21], &t[17], &t[19], 00)); - ASSERT_TRUE(t[30].checkLinkageUpLeftRightBF(&t[21], &t[22], &t[31], -1)); - ASSERT_TRUE(t[22].checkLinkageUpLeftRightBF(&t[30], Zzzzzz, &t[23], +1)); - ASSERT_TRUE(t[16].checkLinkageUpLeftRightBF(Zzzzzz, &t[8], &t[21], 00)); - ASSERT_TRUE(nullptr == findBrokenBalanceFactor(root)); - ASSERT_TRUE(nullptr == findBrokenAncestry(root)); - ASSERT_TRUE(24 == checkAscension(root)); + TEST_ASSERT(t[21].checkLinkageUpLeftRightBF(&t[16], &t[18], &t[30], +1)); + TEST_ASSERT(t[18].checkLinkageUpLeftRightBF(&t[21], &t[17], &t[19], 00)); + TEST_ASSERT(t[30].checkLinkageUpLeftRightBF(&t[21], &t[22], &t[31], -1)); + TEST_ASSERT(t[22].checkLinkageUpLeftRightBF(&t[30], Zzzzzz, &t[23], +1)); + TEST_ASSERT(t[16].checkLinkageUpLeftRightBF(Zzzzzz, &t[8], &t[21], 00)); + TEST_ASSERT(nullptr == findBrokenBalanceFactor(root)); + TEST_ASSERT(nullptr == findBrokenAncestry(root)); + TEST_ASSERT(24 == checkAscension(root)); // REMOVE 8 // 16 @@ -1218,15 +1222,15 @@ TEST(Cavl, MutationManual) // / ` / ` ` / ` ` // 1 3 5 7 11 13 15 23 std::puts("REMOVE 8:"); - ASSERT_TRUE(t[8].checkLinkageUpLeftRightBF(&t[16], &t[4], &t[12], 00)); + TEST_ASSERT(t[8].checkLinkageUpLeftRightBF(&t[16], &t[4], &t[12], 00)); remove(&root, &t[8]); - ASSERT_TRUE(&t[16] == root); + TEST_ASSERT(&t[16] == root); print(root); - ASSERT_TRUE(t[9].checkLinkageUpLeftRightBF(&t[16], &t[4], &t[12], 00)); - ASSERT_TRUE(t[10].checkLinkageUpLeftRightBF(&t[12], Zzzzz, &t[11], +1)); - ASSERT_TRUE(nullptr == findBrokenBalanceFactor(root)); - ASSERT_TRUE(nullptr == findBrokenAncestry(root)); - ASSERT_TRUE(23 == checkAscension(root)); + TEST_ASSERT(t[9].checkLinkageUpLeftRightBF(&t[16], &t[4], &t[12], 00)); + TEST_ASSERT(t[10].checkLinkageUpLeftRightBF(&t[12], Zzzzz, &t[11], +1)); + TEST_ASSERT(nullptr == findBrokenBalanceFactor(root)); + TEST_ASSERT(nullptr == findBrokenAncestry(root)); + TEST_ASSERT(23 == checkAscension(root)); // REMOVE 9 // 16 @@ -1239,15 +1243,15 @@ TEST(Cavl, MutationManual) // / ` / ` / ` ` // 1 3 5 7 13 15 23 std::puts("REMOVE 9:"); - ASSERT_TRUE(t[9].checkLinkageUpLeftRightBF(&t[16], &t[4], &t[12], 00)); + TEST_ASSERT(t[9].checkLinkageUpLeftRightBF(&t[16], &t[4], &t[12], 00)); remove(&root, &t[9]); - ASSERT_TRUE(&t[16] == root); + TEST_ASSERT(&t[16] == root); print(root); - ASSERT_TRUE(t[10].checkLinkageUpLeftRightBF(&t[16], &t[4], &t[12], 00)); - ASSERT_TRUE(t[12].checkLinkageUpLeftRightBF(&t[10], &t[11], &t[14], +1)); - ASSERT_TRUE(nullptr == findBrokenBalanceFactor(root)); - ASSERT_TRUE(nullptr == findBrokenAncestry(root)); - ASSERT_TRUE(22 == checkAscension(root)); + TEST_ASSERT(t[10].checkLinkageUpLeftRightBF(&t[16], &t[4], &t[12], 00)); + TEST_ASSERT(t[12].checkLinkageUpLeftRightBF(&t[10], &t[11], &t[14], +1)); + TEST_ASSERT(nullptr == findBrokenBalanceFactor(root)); + TEST_ASSERT(nullptr == findBrokenAncestry(root)); + TEST_ASSERT(22 == checkAscension(root)); // REMOVE 1 // 16 @@ -1260,14 +1264,14 @@ TEST(Cavl, MutationManual) // ` / ` / ` ` // 3 5 7 13 15 23 std::puts("REMOVE 1:"); - ASSERT_TRUE(t[1].checkLinkageUpLeftRightBF(&t[2], Zzzzz, Zzzzz, 00)); + TEST_ASSERT(t[1].checkLinkageUpLeftRightBF(&t[2], Zzzzz, Zzzzz, 00)); remove(&root, &t[1]); - ASSERT_TRUE(&t[16] == root); + TEST_ASSERT(&t[16] == root); print(root); - ASSERT_TRUE(t[2].checkLinkageUpLeftRightBF(&t[4], Zzzzz, &t[3], +1)); - ASSERT_TRUE(nullptr == findBrokenBalanceFactor(root)); - ASSERT_TRUE(nullptr == findBrokenAncestry(root)); - ASSERT_TRUE(21 == checkAscension(root)); + TEST_ASSERT(t[2].checkLinkageUpLeftRightBF(&t[4], Zzzzz, &t[3], +1)); + TEST_ASSERT(nullptr == findBrokenBalanceFactor(root)); + TEST_ASSERT(nullptr == findBrokenAncestry(root)); + TEST_ASSERT(21 == checkAscension(root)); } auto getRandomByte() @@ -1275,7 +1279,7 @@ auto getRandomByte() return static_cast((0xFFLL * std::rand()) / RAND_MAX); } -TEST(Cavl, MutationRandomized) +void testMutationRandomized() { using N = Node; std::array t{}; @@ -1291,15 +1295,15 @@ TEST(Cavl, MutationRandomized) std::uint64_t cnt_removal = 0; const auto validate = [&] { - ASSERT_TRUE(size == std::accumulate(mask.begin(), mask.end(), 0U, [](const std::size_t a, const std::size_t b) { + TEST_ASSERT(size == std::accumulate(mask.begin(), mask.end(), 0U, [](const std::size_t a, const std::size_t b) { return a + b; })); - ASSERT_TRUE(nullptr == findBrokenBalanceFactor(root)); - ASSERT_TRUE(nullptr == findBrokenAncestry(root)); - ASSERT_TRUE(size == checkAscension(root)); + TEST_ASSERT(nullptr == findBrokenBalanceFactor(root)); + TEST_ASSERT(nullptr == findBrokenAncestry(root)); + TEST_ASSERT(size == checkAscension(root)); std::array new_mask{}; traverse(root, [&](const N* node) { new_mask.at(node->value) = true; }); - ASSERT_TRUE(mask == new_mask); // Otherwise, the contents of the tree does not match our expectations. + TEST_ASSERT(mask == new_mask); // Otherwise, the contents of the tree does not match our expectations. }; validate(); @@ -1307,21 +1311,21 @@ TEST(Cavl, MutationRandomized) const auto predicate = [&](const N& v) { return x - v.value; }; if (N* const existing = search(&root, predicate)) { - ASSERT_TRUE(mask.at(x)); - ASSERT_TRUE(x == existing->value); - ASSERT_TRUE(x == search(&root, predicate, []() -> N* { + TEST_ASSERT(mask.at(x)); + TEST_ASSERT(x == existing->value); + TEST_ASSERT(x == search(&root, predicate, []() -> N* { throw std::logic_error("Attempted to create a new node when there is one already"); })->value); } else { - ASSERT_TRUE(!mask.at(x)); + TEST_ASSERT(!mask.at(x)); bool factory_called = false; - ASSERT_TRUE(x == search(&root, predicate, [&]() -> N* { - factory_called = true; + TEST_ASSERT(x == search(&root, predicate, [&]() -> N* { + factory_called = true; // NOLINT(bugprone-assignment-in-if-condition) return &t.at(x); })->value); - ASSERT_TRUE(factory_called); + TEST_ASSERT(factory_called); size++; cnt_addition++; mask.at(x) = true; @@ -1332,17 +1336,17 @@ TEST(Cavl, MutationRandomized) const auto predicate = [&](const N& v) { return x - v.value; }; if (N* const existing = search(&root, predicate)) { - ASSERT_TRUE(mask.at(x)); - ASSERT_TRUE(x == existing->value); + TEST_ASSERT(mask.at(x)); + TEST_ASSERT(x == existing->value); remove(&root, existing); size--; cnt_removal++; mask.at(x) = false; - ASSERT_TRUE(nullptr == search(&root, predicate)); + TEST_ASSERT(nullptr == search(&root, predicate)); } else { - ASSERT_TRUE(!mask.at(x)); + TEST_ASSERT(!mask.at(x)); } }; @@ -1373,3 +1377,28 @@ TEST(Cavl, MutationRandomized) } validate(); } + +} // namespace + +void setUp() {} + +void tearDown() {} + +int main(const int argc, const char* const argv[]) +{ + const auto seed = static_cast((argc > 1) ? std::atoll(argv[1]) : std::time(nullptr)); // NOLINT + std::cout << "Randomness seed: " << seed << std::endl; + std::srand(seed); + UNITY_BEGIN(); + RUN_TEST(testCheckAscension); + RUN_TEST(testRotation); + RUN_TEST(testBalancingA); + RUN_TEST(testBalancingB); + RUN_TEST(testBalancingC); + RUN_TEST(testRetracingOnGrowth); + RUN_TEST(testSearchTrivial); + RUN_TEST(testRemovalA); + RUN_TEST(testMutationManual); + RUN_TEST(testMutationRandomized); + return UNITY_END(); +} diff --git a/tests/src/test_helpers.c b/tests/src/test_helpers.c new file mode 100644 index 0000000..73d9369 --- /dev/null +++ b/tests/src/test_helpers.c @@ -0,0 +1,62 @@ +// This software is distributed under the terms of the MIT License. +// Copyright (c) 2016-2020 OpenCyphal Development Team. + +#include "helpers.h" +#include + +static void testInstrumentedAllocator(void) +{ + InstrumentedAllocator al; + instrumentedAllocatorNew(&al); + TEST_ASSERT_EQUAL_size_t(0, al.allocated_fragments); + TEST_ASSERT_EQUAL_size_t(SIZE_MAX, al.limit_bytes); + + void* a = al.base.allocate(&al.base, 123); + TEST_ASSERT_EQUAL_size_t(1, al.allocated_fragments); + TEST_ASSERT_EQUAL_size_t(123, al.allocated_bytes); + + void* b = al.base.allocate(&al.base, 456); + TEST_ASSERT_EQUAL_size_t(2, al.allocated_fragments); + TEST_ASSERT_EQUAL_size_t(579, al.allocated_bytes); + + al.limit_bytes = 600; + + TEST_ASSERT_EQUAL_PTR(NULL, al.base.allocate(&al.base, 100)); + TEST_ASSERT_EQUAL_size_t(2, al.allocated_fragments); + TEST_ASSERT_EQUAL_size_t(579, al.allocated_bytes); + + void* c = al.base.allocate(&al.base, 21); + TEST_ASSERT_EQUAL_size_t(3, al.allocated_fragments); + TEST_ASSERT_EQUAL_size_t(600, al.allocated_bytes); + + al.base.free(&al.base, 123, a); + TEST_ASSERT_EQUAL_size_t(2, al.allocated_fragments); + TEST_ASSERT_EQUAL_size_t(477, al.allocated_bytes); + + void* d = al.base.allocate(&al.base, 100); + TEST_ASSERT_EQUAL_size_t(3, al.allocated_fragments); + TEST_ASSERT_EQUAL_size_t(577, al.allocated_bytes); + + al.base.free(&al.base, 21, c); + TEST_ASSERT_EQUAL_size_t(2, al.allocated_fragments); + TEST_ASSERT_EQUAL_size_t(556, al.allocated_bytes); + + al.base.free(&al.base, 100, d); + TEST_ASSERT_EQUAL_size_t(1, al.allocated_fragments); + TEST_ASSERT_EQUAL_size_t(456, al.allocated_bytes); + + al.base.free(&al.base, 456, b); + TEST_ASSERT_EQUAL_size_t(0, al.allocated_fragments); + TEST_ASSERT_EQUAL_size_t(0, al.allocated_bytes); +} + +void setUp(void) {} + +void tearDown(void) {} + +int main(void) +{ + UNITY_BEGIN(); + RUN_TEST(testInstrumentedAllocator); + return UNITY_END(); +} diff --git a/tests/src/test_intrusive_crc.c b/tests/src/test_intrusive_crc.c new file mode 100644 index 0000000..d871865 --- /dev/null +++ b/tests/src/test_intrusive_crc.c @@ -0,0 +1,40 @@ +/// This software is distributed under the terms of the MIT License. +/// Copyright (C) OpenCyphal Development Team +/// Copyright Amazon.com Inc. or its affiliates. +/// SPDX-License-Identifier: MIT + +#include // NOLINT(bugprone-suspicious-include) +#include + +static void testHeaderCRC(void) +{ + TEST_ASSERT_EQUAL_UINT16(0x29B1U, headerCRCCompute(9, "123456789")); +} + +static void testTransferCRC(void) +{ + uint32_t crc = transferCRCAdd(TRANSFER_CRC_INITIAL, 3, "123"); + crc = transferCRCAdd(crc, 6, "456789"); + TEST_ASSERT_EQUAL_UINT32(0x1CF96D7CUL, crc); + TEST_ASSERT_EQUAL_UINT32(0xE3069283UL, crc ^ TRANSFER_CRC_OUTPUT_XOR); + crc = transferCRCAdd(crc, + 4, + "\x83" // Least significant byte first. + "\x92" + "\x06" + "\xE3"); + TEST_ASSERT_EQUAL_UINT32(0xB798B438UL, crc); + TEST_ASSERT_EQUAL_UINT32(0x48674BC7UL, crc ^ TRANSFER_CRC_OUTPUT_XOR); +} + +void setUp(void) {} + +void tearDown(void) {} + +int main(void) +{ + UNITY_BEGIN(); + RUN_TEST(testHeaderCRC); + RUN_TEST(testTransferCRC); + return UNITY_END(); +} diff --git a/tests/src/test_intrusive_tx.c b/tests/src/test_intrusive_tx.c new file mode 100644 index 0000000..3126c2a --- /dev/null +++ b/tests/src/test_intrusive_tx.c @@ -0,0 +1,918 @@ +/// This software is distributed under the terms of the MIT License. +/// Copyright (C) OpenCyphal Development Team +/// Copyright Amazon.com Inc. or its affiliates. +/// SPDX-License-Identifier: MIT + +#include // NOLINT(bugprone-suspicious-include) +#include "helpers.h" +#include + +// >>> from pycyphal.transport.commons.crc import CRC32C +// >>> list(CRC32C.new(data).value_as_bytes) +static const char EtherealStrength[] = + "All was silent except for the howl of the wind against the antenna. Ye watched as the remaining birds in the " + "flock gradually settled back into the forest. She stared at the antenna and thought it looked like an enormous " + "hand stretched open toward the sky, possessing an ethereal strength."; +static const size_t EtherealStrengthSize = sizeof(EtherealStrength) - 1; +static const byte_t EtherealStrengthCRC[4] = {209, 88, 130, 43}; + +static const char DetailOfTheCosmos[] = + "For us, the dark forest state is all-important, but it's just a detail of the cosmos."; +static const size_t DetailOfTheCosmosSize = sizeof(DetailOfTheCosmos) - 1; +static const byte_t DetailOfTheCosmosCRC[4] = {125, 113, 207, 171}; + +static const char InterstellarWar[] = "You have not seen what a true interstellar war is like."; +static const size_t InterstellarWarSize = sizeof(InterstellarWar) - 1; +static const byte_t InterstellarWarCRC[4] = {102, 217, 109, 188}; + +typedef struct +{ + byte_t data[HEADER_SIZE_BYTES]; +} HeaderBuffer; + +static HeaderBuffer makeHeader(const TransferMetadata meta, const uint32_t frame_index, const bool end_of_transfer) +{ + HeaderBuffer buffer; + (void) txSerializeHeader(&buffer.data[0], meta, frame_index, end_of_transfer); + return buffer; +} + +// Generate reference data using PyCyphal: +// +// >>> from pycyphal.transport.udp import UDPFrame +// >>> from pycyphal.transport import Priority, MessageDataSpecifier +// >>> frame = UDPFrame(priority=Priority.FAST, transfer_id=0xbadc0ffee0ddf00d, index=12345, end_of_transfer=False, +// payload=memoryview(b''), source_node_id=2345, destination_node_id=5432, +// data_specifier=MessageDataSpecifier(7654), user_data=0) +// >>> list(frame.compile_header_and_payload()[0]) +// [1, 2, 41, 9, 56, 21, 230, 29, 13, 240, 221, 224, 254, 15, 220, 186, 57, 48, 0, 0, 0, 0, 224, 60] +static void testTxSerializeHeader(void) +{ + { + HeaderBuffer buffer; + TEST_ASSERT_EQUAL_PTR(&buffer.data[0] + HEADER_SIZE_BYTES, + txSerializeHeader(buffer.data, + (TransferMetadata){ + .priority = UdpardPriorityFast, + .src_node_id = 2345, + .dst_node_id = 5432, + .data_specifier = 7654, + .transfer_id = 0xBADC0FFEE0DDF00dULL, + }, + 12345, + false)); + const HeaderBuffer ref = { + .data = {1, 2, 41, 9, 56, 21, 230, 29, 13, 240, 221, 224, 254, 15, 220, 186, 57, 48, 0, 0, 0, 0, 224, 60}}; + TEST_ASSERT_EQUAL_MEMORY(ref.data, buffer.data, HEADER_SIZE_BYTES); + } + { + HeaderBuffer buffer; + TEST_ASSERT_EQUAL(&buffer.data[0] + HEADER_SIZE_BYTES, + txSerializeHeader(buffer.data, + (TransferMetadata){ + .priority = UdpardPriorityLow, + .src_node_id = 0xFEDC, + .dst_node_id = 0xBA98, + .data_specifier = 1234, + .transfer_id = 0x0BADC0DE0BADC0DEULL, + }, + 0x7FFF, + true)); + const HeaderBuffer ref = {.data = {1, 5, 220, 254, 152, 186, 210, 4, 222, 192, 173, 11, + 222, 192, 173, 11, 255, 127, 0, 128, 0, 0, 229, 4}}; + TEST_ASSERT_EQUAL_MEMORY(ref.data, buffer.data, HEADER_SIZE_BYTES); + } +} + +static void testMakeChainEmpty(void) +{ + InstrumentedAllocator alloc; + instrumentedAllocatorNew(&alloc); + char user_transfer_referent = '\0'; + const TransferMetadata meta = { + .priority = UdpardPriorityFast, + .src_node_id = 1234, + .dst_node_id = 2345, + .data_specifier = 5432, + .transfer_id = 0xBADC0FFEE0DDF00DULL, + }; + const TxChain chain = txMakeChain(&alloc.base, + (byte_t[]){11, 22, 33, 44, 55, 66, 77, 88}, + 30, + 1234567890, + meta, + (UdpardUDPIPEndpoint){.ip_address = 0x0A0B0C0DU, .udp_port = 0x1234}, + (UdpardConstPayload){.size = 0, .data = ""}, + &user_transfer_referent); + TEST_ASSERT_EQUAL(1, alloc.allocated_fragments); + TEST_ASSERT_EQUAL(sizeof(TxItem) + HEADER_SIZE_BYTES + 4, alloc.allocated_bytes); + TEST_ASSERT_EQUAL(1, chain.count); + TEST_ASSERT_EQUAL(chain.head, chain.tail); + TEST_ASSERT_EQUAL(NULL, chain.head->base.next_in_transfer); + TEST_ASSERT_EQUAL(1234567890, chain.head->base.deadline_usec); + TEST_ASSERT_EQUAL(33, chain.head->base.dscp); + TEST_ASSERT_EQUAL(0x0A0B0C0DU, chain.head->base.destination.ip_address); + TEST_ASSERT_EQUAL(0x1234, chain.head->base.destination.udp_port); + TEST_ASSERT_EQUAL(HEADER_SIZE_BYTES + 4, chain.head->base.datagram_payload.size); + TEST_ASSERT_EQUAL(0, + memcmp(makeHeader(meta, 0, true).data, + chain.head->base.datagram_payload.data, + HEADER_SIZE_BYTES)); + TEST_ASSERT_EQUAL(0, + memcmp("\x00\x00\x00\x00", // CRC of the empty transfer. + (byte_t*) (chain.head->base.datagram_payload.data) + HEADER_SIZE_BYTES, + 4)); + TEST_ASSERT_EQUAL(&user_transfer_referent, chain.head->base.user_transfer_reference); +} + +static void testMakeChainSingleMaxMTU(void) +{ + InstrumentedAllocator alloc; + instrumentedAllocatorNew(&alloc); + char user_transfer_referent = '\0'; + const TransferMetadata meta = { + .priority = UdpardPrioritySlow, + .src_node_id = 4321, + .dst_node_id = 5432, + .data_specifier = 7766, + .transfer_id = 0x0123456789ABCDEFULL, + }; + const TxChain chain = txMakeChain(&alloc.base, + (byte_t[]){11, 22, 33, 44, 55, 66, 77, 88}, + DetailOfTheCosmosSize + TRANSFER_CRC_SIZE_BYTES, + 1234567890, + meta, + (UdpardUDPIPEndpoint){.ip_address = 0x0A0B0C00U, .udp_port = 7474}, + (UdpardConstPayload){.size = DetailOfTheCosmosSize, .data = DetailOfTheCosmos}, + &user_transfer_referent); + TEST_ASSERT_EQUAL(1, alloc.allocated_fragments); + TEST_ASSERT_EQUAL(sizeof(TxItem) + HEADER_SIZE_BYTES + DetailOfTheCosmosSize + TRANSFER_CRC_SIZE_BYTES, + alloc.allocated_bytes); + TEST_ASSERT_EQUAL(1, chain.count); + TEST_ASSERT_EQUAL(chain.head, chain.tail); + TEST_ASSERT_EQUAL(NULL, chain.head->base.next_in_transfer); + TEST_ASSERT_EQUAL(1234567890, chain.head->base.deadline_usec); + TEST_ASSERT_EQUAL(77, chain.head->base.dscp); + TEST_ASSERT_EQUAL(0x0A0B0C00U, chain.head->base.destination.ip_address); + TEST_ASSERT_EQUAL(7474, chain.head->base.destination.udp_port); + TEST_ASSERT_EQUAL(HEADER_SIZE_BYTES + DetailOfTheCosmosSize + TRANSFER_CRC_SIZE_BYTES, + chain.head->base.datagram_payload.size); + TEST_ASSERT_EQUAL(0, + memcmp(makeHeader(meta, 0, true).data, + chain.head->base.datagram_payload.data, + HEADER_SIZE_BYTES)); + TEST_ASSERT_EQUAL(0, + memcmp(DetailOfTheCosmos, + (byte_t*) (chain.head->base.datagram_payload.data) + HEADER_SIZE_BYTES, + DetailOfTheCosmosSize)); + TEST_ASSERT_EQUAL(0, + memcmp(DetailOfTheCosmosCRC, + (byte_t*) (chain.head->base.datagram_payload.data) + HEADER_SIZE_BYTES + + DetailOfTheCosmosSize, + TRANSFER_CRC_SIZE_BYTES)); + TEST_ASSERT_EQUAL(&user_transfer_referent, chain.head->base.user_transfer_reference); +} + +static void testMakeChainThreeFrames(void) +{ + InstrumentedAllocator alloc; + instrumentedAllocatorNew(&alloc); + char user_transfer_referent = '\0'; + const TransferMetadata meta = { + .priority = UdpardPriorityNominal, + .src_node_id = 4321, + .dst_node_id = 5432, + .data_specifier = 7766, + .transfer_id = 0x0123456789ABCDEFULL, + }; + const size_t mtu = (EtherealStrengthSize + 4U + 3U) / 3U; // Force payload split into three frames. + const TxChain chain = txMakeChain(&alloc.base, + (byte_t[]){11, 22, 33, 44, 55, 66, 77, 88}, + mtu, + 223574680, + meta, + (UdpardUDPIPEndpoint){.ip_address = 0xBABADEDAU, .udp_port = 0xD0ED}, + (UdpardConstPayload){.size = EtherealStrengthSize, .data = EtherealStrength}, + &user_transfer_referent); + TEST_ASSERT_EQUAL(3, alloc.allocated_fragments); + TEST_ASSERT_EQUAL(3 * (sizeof(TxItem) + HEADER_SIZE_BYTES) + EtherealStrengthSize + 4U, alloc.allocated_bytes); + TEST_ASSERT_EQUAL(3, chain.count); + const UdpardTxItem* const first = &chain.head->base; + TEST_ASSERT_NOT_EQUAL(NULL, first); + const UdpardTxItem* const second = first->next_in_transfer; + TEST_ASSERT_NOT_EQUAL(NULL, second); + const UdpardTxItem* const third = second->next_in_transfer; + TEST_ASSERT_NOT_EQUAL(NULL, third); + TEST_ASSERT_EQUAL(NULL, third->next_in_transfer); + TEST_ASSERT_EQUAL((UdpardTxItem*) chain.tail, third); + + // FIRST FRAME -- contains the first part of the payload. + TEST_ASSERT_EQUAL(223574680, first->deadline_usec); + TEST_ASSERT_EQUAL(55, first->dscp); + TEST_ASSERT_EQUAL(0xBABADEDAU, first->destination.ip_address); + TEST_ASSERT_EQUAL(0xD0ED, first->destination.udp_port); + TEST_ASSERT_EQUAL(HEADER_SIZE_BYTES + mtu, first->datagram_payload.size); + TEST_ASSERT_EQUAL(0, memcmp(makeHeader(meta, 0, false).data, first->datagram_payload.data, HEADER_SIZE_BYTES)); + TEST_ASSERT_EQUAL(0, memcmp(EtherealStrength, (byte_t*) (first->datagram_payload.data) + HEADER_SIZE_BYTES, mtu)); + TEST_ASSERT_EQUAL(&user_transfer_referent, first->user_transfer_reference); + + // SECOND FRAME -- contains the second part of the payload. + TEST_ASSERT_EQUAL(223574680, second->deadline_usec); + TEST_ASSERT_EQUAL(55, second->dscp); + TEST_ASSERT_EQUAL(0xBABADEDAU, second->destination.ip_address); + TEST_ASSERT_EQUAL(0xD0ED, second->destination.udp_port); + TEST_ASSERT_EQUAL(HEADER_SIZE_BYTES + mtu, second->datagram_payload.size); + TEST_ASSERT_EQUAL(0, memcmp(makeHeader(meta, 1, false).data, second->datagram_payload.data, HEADER_SIZE_BYTES)); + TEST_ASSERT_EQUAL(0, + memcmp(EtherealStrength + mtu, + (byte_t*) (second->datagram_payload.data) + HEADER_SIZE_BYTES, + mtu)); + TEST_ASSERT_EQUAL(&user_transfer_referent, second->user_transfer_reference); + + // THIRD FRAME -- contains the third part of the payload and the CRC at the end. + TEST_ASSERT_EQUAL(223574680, third->deadline_usec); + TEST_ASSERT_EQUAL(55, third->dscp); + TEST_ASSERT_EQUAL(0xBABADEDAU, third->destination.ip_address); + TEST_ASSERT_EQUAL(0xD0ED, third->destination.udp_port); + const size_t third_payload_size = EtherealStrengthSize - 2 * mtu; + TEST_ASSERT_EQUAL(HEADER_SIZE_BYTES + third_payload_size + 4U, third->datagram_payload.size); + TEST_ASSERT_EQUAL(0, memcmp(makeHeader(meta, 2, true).data, third->datagram_payload.data, HEADER_SIZE_BYTES)); + TEST_ASSERT_EQUAL(0, + memcmp(EtherealStrength + 2 * mtu, + (byte_t*) (third->datagram_payload.data) + HEADER_SIZE_BYTES, + third_payload_size)); + TEST_ASSERT_EQUAL(0, + memcmp(EtherealStrengthCRC, + (byte_t*) (third->datagram_payload.data) + HEADER_SIZE_BYTES + third_payload_size, + TRANSFER_CRC_SIZE_BYTES)); + TEST_ASSERT_EQUAL(&user_transfer_referent, third->user_transfer_reference); +} + +static void testMakeChainCRCSpill1(void) +{ + InstrumentedAllocator alloc; + instrumentedAllocatorNew(&alloc); + char user_transfer_referent = '\0'; + const TransferMetadata meta = { + .priority = UdpardPriorityNominal, + .src_node_id = 4321, + .dst_node_id = 5432, + .data_specifier = 7766, + .transfer_id = 0x0123456789ABCDEFULL, + }; + const size_t mtu = InterstellarWarSize + 3U; + const TxChain chain = txMakeChain(&alloc.base, + (byte_t[]){11, 22, 33, 44, 55, 66, 77, 88}, + mtu, + 223574680, + meta, + (UdpardUDPIPEndpoint){.ip_address = 0xBABADEDAU, .udp_port = 0xD0ED}, + (UdpardConstPayload){.size = InterstellarWarSize, .data = InterstellarWar}, + &user_transfer_referent); + TEST_ASSERT_EQUAL(2, alloc.allocated_fragments); + TEST_ASSERT_EQUAL(2 * (sizeof(TxItem) + HEADER_SIZE_BYTES) + InterstellarWarSize + 4U, alloc.allocated_bytes); + TEST_ASSERT_EQUAL(2, chain.count); + TEST_ASSERT_NOT_EQUAL(chain.head, chain.tail); + TEST_ASSERT_EQUAL((UdpardTxItem*) chain.tail, chain.head->base.next_in_transfer); + TEST_ASSERT_EQUAL(NULL, chain.tail->base.next_in_transfer); + + // FIRST FRAME -- contains the payload and the first three bytes of the CRC. + TEST_ASSERT_EQUAL(223574680, chain.head->base.deadline_usec); + TEST_ASSERT_EQUAL(55, chain.head->base.dscp); + TEST_ASSERT_EQUAL(0xBABADEDAU, chain.head->base.destination.ip_address); + TEST_ASSERT_EQUAL(0xD0ED, chain.head->base.destination.udp_port); + TEST_ASSERT_EQUAL(HEADER_SIZE_BYTES + mtu, chain.head->base.datagram_payload.size); + TEST_ASSERT_EQUAL(0, + memcmp(makeHeader(meta, 0, false).data, + chain.head->base.datagram_payload.data, + HEADER_SIZE_BYTES)); + TEST_ASSERT_EQUAL(0, + memcmp(InterstellarWar, + (byte_t*) (chain.head->base.datagram_payload.data) + HEADER_SIZE_BYTES, + InterstellarWarSize)); + TEST_ASSERT_EQUAL(0, + memcmp(InterstellarWarCRC, + (byte_t*) (chain.head->base.datagram_payload.data) + HEADER_SIZE_BYTES + + InterstellarWarSize, + 3U)); + TEST_ASSERT_EQUAL(&user_transfer_referent, chain.head->base.user_transfer_reference); + + // SECOND FRAME -- contains the last byte of the CRC. + TEST_ASSERT_EQUAL(223574680, chain.tail->base.deadline_usec); + TEST_ASSERT_EQUAL(55, chain.tail->base.dscp); + TEST_ASSERT_EQUAL(0xBABADEDAU, chain.tail->base.destination.ip_address); + TEST_ASSERT_EQUAL(0xD0ED, chain.tail->base.destination.udp_port); + TEST_ASSERT_EQUAL(HEADER_SIZE_BYTES + 1U, chain.tail->base.datagram_payload.size); + TEST_ASSERT_EQUAL(0, + memcmp(makeHeader(meta, 1, true).data, + chain.tail->base.datagram_payload.data, + HEADER_SIZE_BYTES)); + TEST_ASSERT_EQUAL(0, + memcmp(InterstellarWarCRC + 3U, + (byte_t*) (chain.tail->base.datagram_payload.data) + HEADER_SIZE_BYTES, + 1U)); + TEST_ASSERT_EQUAL(&user_transfer_referent, chain.tail->base.user_transfer_reference); +} + +static void testMakeChainCRCSpill2(void) +{ + InstrumentedAllocator alloc; + instrumentedAllocatorNew(&alloc); + char user_transfer_referent = '\0'; + const TransferMetadata meta = { + .priority = UdpardPriorityNominal, + .src_node_id = 4321, + .dst_node_id = 5432, + .data_specifier = 7766, + .transfer_id = 0x0123456789ABCDEFULL, + }; + const size_t mtu = InterstellarWarSize + 2U; + const TxChain chain = txMakeChain(&alloc.base, + (byte_t[]){11, 22, 33, 44, 55, 66, 77, 88}, + mtu, + 223574680, + meta, + (UdpardUDPIPEndpoint){.ip_address = 0xBABADEDAU, .udp_port = 0xD0ED}, + (UdpardConstPayload){.size = InterstellarWarSize, .data = InterstellarWar}, + &user_transfer_referent); + TEST_ASSERT_EQUAL(2, alloc.allocated_fragments); + TEST_ASSERT_EQUAL(2 * (sizeof(TxItem) + HEADER_SIZE_BYTES) + InterstellarWarSize + 4U, alloc.allocated_bytes); + TEST_ASSERT_EQUAL(2, chain.count); + TEST_ASSERT_NOT_EQUAL(chain.head, chain.tail); + TEST_ASSERT_EQUAL((UdpardTxItem*) chain.tail, chain.head->base.next_in_transfer); + TEST_ASSERT_EQUAL(NULL, chain.tail->base.next_in_transfer); + + // FIRST FRAME -- contains the payload and the first two bytes of the CRC. + TEST_ASSERT_EQUAL(223574680, chain.head->base.deadline_usec); + TEST_ASSERT_EQUAL(55, chain.head->base.dscp); + TEST_ASSERT_EQUAL(0xBABADEDAU, chain.head->base.destination.ip_address); + TEST_ASSERT_EQUAL(0xD0ED, chain.head->base.destination.udp_port); + TEST_ASSERT_EQUAL(HEADER_SIZE_BYTES + mtu, chain.head->base.datagram_payload.size); + TEST_ASSERT_EQUAL(0, + memcmp(makeHeader(meta, 0, false).data, + chain.head->base.datagram_payload.data, + HEADER_SIZE_BYTES)); + TEST_ASSERT_EQUAL(0, + memcmp(InterstellarWar, + (byte_t*) (chain.head->base.datagram_payload.data) + HEADER_SIZE_BYTES, + InterstellarWarSize)); + TEST_ASSERT_EQUAL(0, + memcmp(InterstellarWarCRC, + (byte_t*) (chain.head->base.datagram_payload.data) + HEADER_SIZE_BYTES + + InterstellarWarSize, + 2U)); + TEST_ASSERT_EQUAL(&user_transfer_referent, chain.head->base.user_transfer_reference); + + // SECOND FRAME -- contains the last two bytes of the CRC. + TEST_ASSERT_EQUAL(223574680, chain.tail->base.deadline_usec); + TEST_ASSERT_EQUAL(55, chain.tail->base.dscp); + TEST_ASSERT_EQUAL(0xBABADEDAU, chain.tail->base.destination.ip_address); + TEST_ASSERT_EQUAL(0xD0ED, chain.tail->base.destination.udp_port); + TEST_ASSERT_EQUAL(HEADER_SIZE_BYTES + 2U, chain.tail->base.datagram_payload.size); + TEST_ASSERT_EQUAL(0, + memcmp(makeHeader(meta, 1, true).data, + chain.tail->base.datagram_payload.data, + HEADER_SIZE_BYTES)); + TEST_ASSERT_EQUAL(0, + memcmp(InterstellarWarCRC + 2U, + (byte_t*) (chain.tail->base.datagram_payload.data) + HEADER_SIZE_BYTES, + 2U)); + TEST_ASSERT_EQUAL(&user_transfer_referent, chain.tail->base.user_transfer_reference); +} + +static void testMakeChainCRCSpill3(void) +{ + InstrumentedAllocator alloc; + instrumentedAllocatorNew(&alloc); + char user_transfer_referent = '\0'; + const TransferMetadata meta = { + .priority = UdpardPriorityNominal, + .src_node_id = 4321, + .dst_node_id = 5432, + .data_specifier = 7766, + .transfer_id = 0x0123456789ABCDEFULL, + }; + const size_t mtu = InterstellarWarSize + 1U; + const TxChain chain = txMakeChain(&alloc.base, + (byte_t[]){11, 22, 33, 44, 55, 66, 77, 88}, + mtu, + 223574680, + meta, + (UdpardUDPIPEndpoint){.ip_address = 0xBABADEDAU, .udp_port = 0xD0ED}, + (UdpardConstPayload){.size = InterstellarWarSize, .data = InterstellarWar}, + &user_transfer_referent); + TEST_ASSERT_EQUAL(2, alloc.allocated_fragments); + TEST_ASSERT_EQUAL(2 * (sizeof(TxItem) + HEADER_SIZE_BYTES) + InterstellarWarSize + 4U, alloc.allocated_bytes); + TEST_ASSERT_EQUAL(2, chain.count); + TEST_ASSERT_NOT_EQUAL(chain.head, chain.tail); + TEST_ASSERT_EQUAL((UdpardTxItem*) chain.tail, chain.head->base.next_in_transfer); + TEST_ASSERT_EQUAL(NULL, chain.tail->base.next_in_transfer); + + // FIRST FRAME -- contains the payload and the first byte of the CRC. + TEST_ASSERT_EQUAL(223574680, chain.head->base.deadline_usec); + TEST_ASSERT_EQUAL(55, chain.head->base.dscp); + TEST_ASSERT_EQUAL(0xBABADEDAU, chain.head->base.destination.ip_address); + TEST_ASSERT_EQUAL(0xD0ED, chain.head->base.destination.udp_port); + TEST_ASSERT_EQUAL(HEADER_SIZE_BYTES + mtu, chain.head->base.datagram_payload.size); + TEST_ASSERT_EQUAL(0, + memcmp(makeHeader(meta, 0, false).data, + chain.head->base.datagram_payload.data, + HEADER_SIZE_BYTES)); + TEST_ASSERT_EQUAL(0, + memcmp(InterstellarWar, + (byte_t*) (chain.head->base.datagram_payload.data) + HEADER_SIZE_BYTES, + InterstellarWarSize)); + TEST_ASSERT_EQUAL(0, + memcmp(InterstellarWarCRC, + (byte_t*) (chain.head->base.datagram_payload.data) + HEADER_SIZE_BYTES + + InterstellarWarSize, + 1U)); + TEST_ASSERT_EQUAL(&user_transfer_referent, chain.head->base.user_transfer_reference); + + // SECOND FRAME -- contains the last three bytes of the CRC. + TEST_ASSERT_EQUAL(223574680, chain.tail->base.deadline_usec); + TEST_ASSERT_EQUAL(55, chain.tail->base.dscp); + TEST_ASSERT_EQUAL(0xBABADEDAU, chain.tail->base.destination.ip_address); + TEST_ASSERT_EQUAL(0xD0ED, chain.tail->base.destination.udp_port); + TEST_ASSERT_EQUAL(HEADER_SIZE_BYTES + 3U, chain.tail->base.datagram_payload.size); + TEST_ASSERT_EQUAL(0, + memcmp(makeHeader(meta, 1, true).data, + chain.tail->base.datagram_payload.data, + HEADER_SIZE_BYTES)); + TEST_ASSERT_EQUAL(0, + memcmp(InterstellarWarCRC + 1U, + (byte_t*) (chain.tail->base.datagram_payload.data) + HEADER_SIZE_BYTES, + 3U)); + TEST_ASSERT_EQUAL(&user_transfer_referent, chain.tail->base.user_transfer_reference); +} + +static void testMakeChainCRCSpillFull(void) +{ + InstrumentedAllocator alloc; + instrumentedAllocatorNew(&alloc); + char user_transfer_referent = '\0'; + const TransferMetadata meta = { + .priority = UdpardPriorityNominal, + .src_node_id = 4321, + .dst_node_id = 5432, + .data_specifier = 7766, + .transfer_id = 0x0123456789ABCDEFULL, + }; + const size_t mtu = InterstellarWarSize; + const TxChain chain = txMakeChain(&alloc.base, + (byte_t[]){11, 22, 33, 44, 55, 66, 77, 88}, + mtu, + 223574680, + meta, + (UdpardUDPIPEndpoint){.ip_address = 0xBABADEDAU, .udp_port = 0xD0ED}, + (UdpardConstPayload){.size = InterstellarWarSize, .data = InterstellarWar}, + &user_transfer_referent); + TEST_ASSERT_EQUAL(2, alloc.allocated_fragments); + TEST_ASSERT_EQUAL(2 * (sizeof(TxItem) + HEADER_SIZE_BYTES) + InterstellarWarSize + 4U, alloc.allocated_bytes); + TEST_ASSERT_EQUAL(2, chain.count); + TEST_ASSERT_NOT_EQUAL(chain.head, chain.tail); + TEST_ASSERT_EQUAL((UdpardTxItem*) chain.tail, chain.head->base.next_in_transfer); + TEST_ASSERT_EQUAL(NULL, chain.tail->base.next_in_transfer); + + // FIRST FRAME -- contains the payload only. + TEST_ASSERT_EQUAL(223574680, chain.head->base.deadline_usec); + TEST_ASSERT_EQUAL(55, chain.head->base.dscp); + TEST_ASSERT_EQUAL(0xBABADEDAU, chain.head->base.destination.ip_address); + TEST_ASSERT_EQUAL(0xD0ED, chain.head->base.destination.udp_port); + TEST_ASSERT_EQUAL(HEADER_SIZE_BYTES + mtu, chain.head->base.datagram_payload.size); + TEST_ASSERT_EQUAL(HEADER_SIZE_BYTES + InterstellarWarSize, chain.head->base.datagram_payload.size); + TEST_ASSERT_EQUAL(0, + memcmp(makeHeader(meta, 0, false).data, + chain.head->base.datagram_payload.data, + HEADER_SIZE_BYTES)); + TEST_ASSERT_EQUAL(0, + memcmp(InterstellarWar, + (byte_t*) (chain.head->base.datagram_payload.data) + HEADER_SIZE_BYTES, + InterstellarWarSize)); + TEST_ASSERT_EQUAL(&user_transfer_referent, chain.head->base.user_transfer_reference); + + // SECOND FRAME -- contains the last byte of the CRC. + TEST_ASSERT_EQUAL(223574680, chain.tail->base.deadline_usec); + TEST_ASSERT_EQUAL(55, chain.tail->base.dscp); + TEST_ASSERT_EQUAL(0xBABADEDAU, chain.tail->base.destination.ip_address); + TEST_ASSERT_EQUAL(0xD0ED, chain.tail->base.destination.udp_port); + TEST_ASSERT_EQUAL(HEADER_SIZE_BYTES + 4U, chain.tail->base.datagram_payload.size); + TEST_ASSERT_EQUAL(0, + memcmp(makeHeader(meta, 1, true).data, + chain.tail->base.datagram_payload.data, + HEADER_SIZE_BYTES)); + TEST_ASSERT_EQUAL(0, + memcmp(InterstellarWarCRC, + (byte_t*) (chain.tail->base.datagram_payload.data) + HEADER_SIZE_BYTES, + 4U)); + TEST_ASSERT_EQUAL(&user_transfer_referent, chain.tail->base.user_transfer_reference); +} + +static void testPushPeekPopFree(void) +{ + InstrumentedAllocator alloc; + instrumentedAllocatorNew(&alloc); + const UdpardNodeID node_id = 1234; + // + UdpardTx tx = { + .local_node_id = &node_id, + .queue_capacity = 3, + .mtu = (EtherealStrengthSize + 4U + 3U) / 3U, + .dscp_value_per_priority = {0, 1, 2, 3, 4, 5, 6, 7}, + .memory = &alloc.base, + .queue_size = 0, + .root = NULL, + }; + const TransferMetadata meta = { + .priority = UdpardPriorityNominal, + .src_node_id = 4321, + .dst_node_id = 5432, + .data_specifier = 7766, + .transfer_id = 0x0123456789ABCDEFULL, + }; + char user_transfer_referent = '\0'; + TEST_ASSERT_EQUAL(3, + txPush(&tx, + 1234567890U, + meta, + (UdpardUDPIPEndpoint){.ip_address = 0xBABADEDAU, .udp_port = 0xD0ED}, + (UdpardConstPayload){.size = EtherealStrengthSize, .data = EtherealStrength}, + &user_transfer_referent)); + TEST_ASSERT_EQUAL(3, alloc.allocated_fragments); + TEST_ASSERT_EQUAL(3 * (sizeof(TxItem) + HEADER_SIZE_BYTES) + EtherealStrengthSize + 4U, alloc.allocated_bytes); + TEST_ASSERT_EQUAL(3, tx.queue_size); + + const UdpardTxItem* frame = udpardTxPeek(&tx); + TEST_ASSERT_NOT_EQUAL(NULL, frame); + TEST_ASSERT_NOT_EQUAL(NULL, frame->next_in_transfer); + TEST_ASSERT_EQUAL(1234567890U, frame->deadline_usec); + TEST_ASSERT_EQUAL(4, frame->dscp); + TEST_ASSERT_EQUAL(0xBABADEDAU, frame->destination.ip_address); + TEST_ASSERT_EQUAL(0xD0ED, frame->destination.udp_port); + TEST_ASSERT_EQUAL(HEADER_SIZE_BYTES + tx.mtu, frame->datagram_payload.size); + TEST_ASSERT_EQUAL(0, memcmp(makeHeader(meta, 0, false).data, frame->datagram_payload.data, HEADER_SIZE_BYTES)); + udpardTxFree(tx.memory, udpardTxPop(&tx, frame)); + + TEST_ASSERT_EQUAL(2, alloc.allocated_fragments); + TEST_ASSERT_EQUAL(2, tx.queue_size); + + frame = udpardTxPeek(&tx); + TEST_ASSERT_NOT_EQUAL(NULL, frame); + TEST_ASSERT_NOT_EQUAL(NULL, frame->next_in_transfer); + TEST_ASSERT_EQUAL(1234567890U, frame->deadline_usec); + TEST_ASSERT_EQUAL(4, frame->dscp); + TEST_ASSERT_EQUAL(0xBABADEDAU, frame->destination.ip_address); + TEST_ASSERT_EQUAL(0xD0ED, frame->destination.udp_port); + TEST_ASSERT_EQUAL(HEADER_SIZE_BYTES + tx.mtu, frame->datagram_payload.size); + TEST_ASSERT_EQUAL(0, memcmp(makeHeader(meta, 1, false).data, frame->datagram_payload.data, HEADER_SIZE_BYTES)); + udpardTxFree(tx.memory, udpardTxPop(&tx, frame)); + + TEST_ASSERT_EQUAL(1, alloc.allocated_fragments); + TEST_ASSERT_EQUAL(1, tx.queue_size); + + frame = udpardTxPeek(&tx); + TEST_ASSERT_NOT_EQUAL(NULL, frame); + TEST_ASSERT_EQUAL(NULL, frame->next_in_transfer); + TEST_ASSERT_EQUAL(1234567890U, frame->deadline_usec); + TEST_ASSERT_EQUAL(4, frame->dscp); + TEST_ASSERT_EQUAL(0xBABADEDAU, frame->destination.ip_address); + TEST_ASSERT_EQUAL(0xD0ED, frame->destination.udp_port); + TEST_ASSERT_EQUAL(HEADER_SIZE_BYTES + EtherealStrengthSize - 2 * tx.mtu + 4U, frame->datagram_payload.size); + TEST_ASSERT_EQUAL(0, memcmp(makeHeader(meta, 2, true).data, frame->datagram_payload.data, HEADER_SIZE_BYTES)); + udpardTxFree(tx.memory, udpardTxPop(&tx, frame)); + + TEST_ASSERT_EQUAL(0, alloc.allocated_fragments); + TEST_ASSERT_EQUAL(0, tx.queue_size); + TEST_ASSERT_EQUAL(NULL, udpardTxPeek(&tx)); +} + +static void testPushPrioritization(void) +{ + InstrumentedAllocator alloc; + instrumentedAllocatorNew(&alloc); + const UdpardNodeID node_id = 1234; + // + UdpardTx tx = { + .local_node_id = &node_id, + .queue_capacity = 7, + .mtu = 140, // This is chosen to match the test data. + .dscp_value_per_priority = {0, 1, 2, 3, 4, 5, 6, 7}, + .memory = &alloc.base, + .queue_size = 0, + .root = NULL, + }; + // A -- Push the first multi-frame transfer at nominal priority level. + const TransferMetadata meta_a = { + .priority = UdpardPriorityNominal, + .src_node_id = 100, + .dst_node_id = UDPARD_NODE_ID_UNSET, + .data_specifier = 200, + .transfer_id = 5000, + }; + TEST_ASSERT_EQUAL(3, + txPush(&tx, + 0, + meta_a, + (UdpardUDPIPEndpoint){.ip_address = 0xAAAAAAAA, .udp_port = 0xAAAA}, + (UdpardConstPayload){.size = EtherealStrengthSize, .data = EtherealStrength}, + NULL)); + TEST_ASSERT_EQUAL(3, alloc.allocated_fragments); + TEST_ASSERT_EQUAL(3, tx.queue_size); + const UdpardTxItem* frame = udpardTxPeek(&tx); + TEST_ASSERT_NOT_EQUAL(NULL, frame); + TEST_ASSERT_EQUAL(0xAAAAAAAA, frame->destination.ip_address); + + // B -- Next, push a higher-priority transfer and ensure it takes precedence. + TEST_ASSERT_EQUAL(1, + txPush(&tx, + 0, + (TransferMetadata){ + .priority = UdpardPriorityHigh, + .src_node_id = 100, + .dst_node_id = UDPARD_NODE_ID_UNSET, + .data_specifier = 200, + .transfer_id = 100000, + }, + (UdpardUDPIPEndpoint){.ip_address = 0xBBBBBBBB, .udp_port = 0xBBBB}, + (UdpardConstPayload){.size = DetailOfTheCosmosSize, .data = DetailOfTheCosmos}, + NULL)); + TEST_ASSERT_EQUAL(4, alloc.allocated_fragments); + TEST_ASSERT_EQUAL(4, tx.queue_size); + frame = udpardTxPeek(&tx); + TEST_ASSERT_NOT_EQUAL(NULL, frame); + TEST_ASSERT_EQUAL(0xBBBBBBBB, frame->destination.ip_address); + + // C -- Next, push a lower-priority transfer and ensure it goes towards the back. + TEST_ASSERT_EQUAL(1, + txPush(&tx, + 1002, + (TransferMetadata){ + .priority = UdpardPriorityLow, + .src_node_id = 100, + .dst_node_id = UDPARD_NODE_ID_UNSET, + .data_specifier = 200, + .transfer_id = 10000, + }, + (UdpardUDPIPEndpoint){.ip_address = 0xCCCCCCCC, .udp_port = 0xCCCC}, + (UdpardConstPayload){.size = InterstellarWarSize, .data = InterstellarWar}, + NULL)); + TEST_ASSERT_EQUAL(5, alloc.allocated_fragments); + TEST_ASSERT_EQUAL(5, tx.queue_size); + frame = udpardTxPeek(&tx); + TEST_ASSERT_NOT_EQUAL(NULL, frame); + TEST_ASSERT_EQUAL(0xBBBBBBBB, frame->destination.ip_address); + + // D -- Add another transfer like the previous one and ensure it goes in the back. + TEST_ASSERT_EQUAL(1, + txPush(&tx, + 1003, + (TransferMetadata){ + .priority = UdpardPriorityLow, + .src_node_id = 100, + .dst_node_id = UDPARD_NODE_ID_UNSET, + .data_specifier = 200, + .transfer_id = 10001, + }, + (UdpardUDPIPEndpoint){.ip_address = 0xDDDDDDDD, .udp_port = 0xDDDD}, + (UdpardConstPayload){.size = InterstellarWarSize, .data = InterstellarWar}, + NULL)); + TEST_ASSERT_EQUAL(6, alloc.allocated_fragments); + TEST_ASSERT_EQUAL(6, tx.queue_size); + frame = udpardTxPeek(&tx); + TEST_ASSERT_NOT_EQUAL(NULL, frame); + TEST_ASSERT_EQUAL(0xBBBBBBBB, frame->destination.ip_address); + + // E -- Add an even higher priority transfer. + TEST_ASSERT_EQUAL(1, + txPush(&tx, + 1003, + (TransferMetadata){ + .priority = UdpardPriorityFast, + .src_node_id = 100, + .dst_node_id = UDPARD_NODE_ID_UNSET, + .data_specifier = 200, + .transfer_id = 1000, + }, + (UdpardUDPIPEndpoint){.ip_address = 0xEEEEEEEE, .udp_port = 0xEEEE}, + (UdpardConstPayload){.size = InterstellarWarSize, .data = InterstellarWar}, + NULL)); + TEST_ASSERT_EQUAL(7, alloc.allocated_fragments); + TEST_ASSERT_EQUAL(7, tx.queue_size); + frame = udpardTxPeek(&tx); + TEST_ASSERT_NOT_EQUAL(NULL, frame); + TEST_ASSERT_EQUAL(0xEEEEEEEE, frame->destination.ip_address); + + // Now, unwind the queue and ensure the frames are popped in the right order. + // E + udpardTxFree(tx.memory, udpardTxPop(&tx, frame)); + TEST_ASSERT_EQUAL(6, alloc.allocated_fragments); + TEST_ASSERT_EQUAL(6, tx.queue_size); + // B + frame = udpardTxPeek(&tx); + TEST_ASSERT_NOT_EQUAL(NULL, frame); + TEST_ASSERT_EQUAL(0xBBBBBBBB, frame->destination.ip_address); + udpardTxFree(tx.memory, udpardTxPop(&tx, frame)); + TEST_ASSERT_EQUAL(5, alloc.allocated_fragments); + TEST_ASSERT_EQUAL(5, tx.queue_size); + // A1, three frames. + frame = udpardTxPeek(&tx); + TEST_ASSERT_NOT_EQUAL(NULL, frame); + TEST_ASSERT_EQUAL(0xAAAAAAAA, frame->destination.ip_address); + TEST_ASSERT_EQUAL(0, memcmp(makeHeader(meta_a, 0, false).data, frame->datagram_payload.data, HEADER_SIZE_BYTES)); + udpardTxFree(tx.memory, udpardTxPop(&tx, frame)); + TEST_ASSERT_EQUAL(4, alloc.allocated_fragments); + TEST_ASSERT_EQUAL(4, tx.queue_size); + // A2 + frame = udpardTxPeek(&tx); + TEST_ASSERT_NOT_EQUAL(NULL, frame); + TEST_ASSERT_EQUAL(0xAAAAAAAA, frame->destination.ip_address); + TEST_ASSERT_EQUAL(0, memcmp(makeHeader(meta_a, 1, false).data, frame->datagram_payload.data, HEADER_SIZE_BYTES)); + udpardTxFree(tx.memory, udpardTxPop(&tx, frame)); + TEST_ASSERT_EQUAL(3, alloc.allocated_fragments); + TEST_ASSERT_EQUAL(3, tx.queue_size); + // A3 + frame = udpardTxPeek(&tx); + TEST_ASSERT_NOT_EQUAL(NULL, frame); + TEST_ASSERT_EQUAL(0xAAAAAAAA, frame->destination.ip_address); + TEST_ASSERT_EQUAL(0, memcmp(makeHeader(meta_a, 2, true).data, frame->datagram_payload.data, HEADER_SIZE_BYTES)); + udpardTxFree(tx.memory, udpardTxPop(&tx, frame)); + TEST_ASSERT_EQUAL(2, alloc.allocated_fragments); + TEST_ASSERT_EQUAL(2, tx.queue_size); + // C + frame = udpardTxPeek(&tx); + TEST_ASSERT_NOT_EQUAL(NULL, frame); + TEST_ASSERT_EQUAL(0xCCCCCCCC, frame->destination.ip_address); + udpardTxFree(tx.memory, udpardTxPop(&tx, frame)); + TEST_ASSERT_EQUAL(1, alloc.allocated_fragments); + TEST_ASSERT_EQUAL(1, tx.queue_size); + // D + frame = udpardTxPeek(&tx); + TEST_ASSERT_NOT_EQUAL(NULL, frame); + TEST_ASSERT_EQUAL(0xDDDDDDDD, frame->destination.ip_address); + udpardTxFree(tx.memory, udpardTxPop(&tx, frame)); + TEST_ASSERT_EQUAL(0, alloc.allocated_fragments); + TEST_ASSERT_EQUAL(0, tx.queue_size); + + TEST_ASSERT_EQUAL(NULL, udpardTxPeek(&tx)); +} + +static void testPushCapacityLimit(void) +{ + InstrumentedAllocator alloc; + instrumentedAllocatorNew(&alloc); + const UdpardNodeID node_id = 1234; + // + UdpardTx tx = { + .local_node_id = &node_id, + .queue_capacity = 2, + .mtu = 10U, + .dscp_value_per_priority = {0, 1, 2, 3, 4, 5, 6, 7}, + .memory = &alloc.base, + .queue_size = 0, + .root = NULL, + }; + const TransferMetadata meta = { + .priority = UdpardPriorityNominal, + .src_node_id = 4321, + .dst_node_id = 5432, + .data_specifier = 7766, + .transfer_id = 0x0123456789ABCDEFULL, + }; + TEST_ASSERT_EQUAL(-UDPARD_ERROR_CAPACITY, + txPush(&tx, + 1234567890U, + meta, + (UdpardUDPIPEndpoint){.ip_address = 0xBABADEDAU, .udp_port = 0xD0ED}, + (UdpardConstPayload){.size = EtherealStrengthSize, .data = EtherealStrength}, + NULL)); + TEST_ASSERT_EQUAL(0, alloc.allocated_fragments); + TEST_ASSERT_EQUAL(0, alloc.allocated_bytes); + TEST_ASSERT_EQUAL(0, tx.queue_size); +} + +static void testPushOOM(void) +{ + InstrumentedAllocator alloc; + instrumentedAllocatorNew(&alloc); + const UdpardNodeID node_id = 1234; + // + UdpardTx tx = { + .local_node_id = &node_id, + .queue_capacity = 10000U, + .mtu = (EtherealStrengthSize + 4U + 3U) / 3U, + .dscp_value_per_priority = {0, 1, 2, 3, 4, 5, 6, 7}, + .memory = &alloc.base, + .queue_size = 0, + .root = NULL, + }; + const TransferMetadata meta = { + .priority = UdpardPriorityNominal, + .src_node_id = 4321, + .dst_node_id = 5432, + .data_specifier = 7766, + .transfer_id = 0x0123456789ABCDEFULL, + }; + alloc.limit_bytes = EtherealStrengthSize; // No memory for the overheads. + TEST_ASSERT_EQUAL(-UDPARD_ERROR_MEMORY, + txPush(&tx, + 1234567890U, + meta, + (UdpardUDPIPEndpoint){.ip_address = 0xBABADEDAU, .udp_port = 0xD0ED}, + (UdpardConstPayload){.size = EtherealStrengthSize, .data = EtherealStrength}, + NULL)); + TEST_ASSERT_EQUAL(0, alloc.allocated_fragments); + TEST_ASSERT_EQUAL(0, alloc.allocated_bytes); + TEST_ASSERT_EQUAL(0, tx.queue_size); +} + +static void testPushAnonymousMultiFrame(void) +{ + InstrumentedAllocator alloc; + instrumentedAllocatorNew(&alloc); + const UdpardNodeID node_id = 0xFFFFU; + // + UdpardTx tx = { + .local_node_id = &node_id, + .queue_capacity = 10000U, + .mtu = (EtherealStrengthSize + 4U + 3U) / 3U, + .dscp_value_per_priority = {0, 1, 2, 3, 4, 5, 6, 7}, + .memory = &alloc.base, + .queue_size = 0, + .root = NULL, + }; + const TransferMetadata meta = { + .priority = UdpardPriorityNominal, + .src_node_id = 4321, + .dst_node_id = 5432, + .data_specifier = 7766, + .transfer_id = 0x0123456789ABCDEFULL, + }; + TEST_ASSERT_EQUAL(-UDPARD_ERROR_ANONYMOUS, + txPush(&tx, + 1234567890U, + meta, + (UdpardUDPIPEndpoint){.ip_address = 0xBABADEDAU, .udp_port = 0xD0ED}, + (UdpardConstPayload){.size = EtherealStrengthSize, .data = EtherealStrength}, + NULL)); + TEST_ASSERT_EQUAL(0, alloc.allocated_fragments); + TEST_ASSERT_EQUAL(0, alloc.allocated_bytes); + TEST_ASSERT_EQUAL(0, tx.queue_size); +} + +static void testPushAnonymousService(void) +{ + InstrumentedAllocator alloc; + instrumentedAllocatorNew(&alloc); + const UdpardNodeID node_id = 0xFFFFU; + // + UdpardTx tx = { + .local_node_id = &node_id, + .queue_capacity = 10000, + .mtu = 1500, + .dscp_value_per_priority = {0, 1, 2, 3, 4, 5, 6, 7}, + .memory = &alloc.base, + .queue_size = 0, + .root = NULL, + }; + const TransferMetadata meta = { + .priority = UdpardPriorityNominal, + .src_node_id = 4321, + .dst_node_id = 5432, + .data_specifier = 0x8099U, // Service response. + .transfer_id = 0x0123456789ABCDEFULL, + }; + TEST_ASSERT_EQUAL(-UDPARD_ERROR_ANONYMOUS, + txPush(&tx, + 1234567890U, + meta, + (UdpardUDPIPEndpoint){.ip_address = 0xBABADEDAU, .udp_port = 0xD0ED}, + (UdpardConstPayload){.size = EtherealStrengthSize, .data = EtherealStrength}, + NULL)); + TEST_ASSERT_EQUAL(0, alloc.allocated_fragments); + TEST_ASSERT_EQUAL(0, alloc.allocated_bytes); + TEST_ASSERT_EQUAL(0, tx.queue_size); +} + +void setUp(void) {} + +void tearDown(void) {} + +int main(void) +{ + UNITY_BEGIN(); + RUN_TEST(testTxSerializeHeader); + RUN_TEST(testMakeChainEmpty); + RUN_TEST(testMakeChainSingleMaxMTU); + RUN_TEST(testMakeChainThreeFrames); + RUN_TEST(testMakeChainCRCSpill1); + RUN_TEST(testMakeChainCRCSpill2); + RUN_TEST(testMakeChainCRCSpill3); + RUN_TEST(testMakeChainCRCSpillFull); + RUN_TEST(testPushPeekPopFree); + RUN_TEST(testPushPrioritization); + RUN_TEST(testPushCapacityLimit); + RUN_TEST(testPushOOM); + RUN_TEST(testPushAnonymousMultiFrame); + RUN_TEST(testPushAnonymousService); + return UNITY_END(); +} diff --git a/tests/src/test_tx.cpp b/tests/src/test_tx.cpp new file mode 100644 index 0000000..6648833 --- /dev/null +++ b/tests/src/test_tx.cpp @@ -0,0 +1,562 @@ +/// This software is distributed under the terms of the MIT License. +/// Copyright (C) OpenCyphal Development Team +/// Copyright Amazon.com Inc. or its affiliates. +/// SPDX-License-Identifier: MIT + +#include +#include "helpers.h" +#include "hexdump.hpp" +#include +#include +#include +#include +#include +#include + +namespace +{ +constexpr std::string_view FleetingEvents = + "What was the human world like in the eyes of the mountains? Perhaps just something they saw on a leisurely " + "afternoon. First, a few small living beings appeared on the plain. After a while, they multiplied, and after " + "another while they erected structures like anthills that quickly filled the region. The structures shone from the " + "inside, and some of them let off smoke. After another while, the lights and smoke disappeared, and the small " + "things vanished as well, and then their structures toppled and were buried in the sand. That was all. Among the " + "countless things the mountains had witnessed, these fleeting events were not necessarily the most interesting."; +constexpr std::array FleetingEventsCRC{{26, 198, 18, 137}}; + +void testInit() +{ + std::monostate user_referent; + const UdpardNodeID node_id = 0; + { + UdpardMemoryResource memory{ + .allocate = &dummyAllocatorAllocate, + .free = &dummyAllocatorFree, + .user_reference = &user_referent, + }; + TEST_ASSERT_EQUAL(-UDPARD_ERROR_ARGUMENT, udpardTxInit(nullptr, &node_id, 0, &memory)); + } + { + UdpardTx tx{}; + UdpardMemoryResource memory{ + .allocate = &dummyAllocatorAllocate, + .free = &dummyAllocatorFree, + .user_reference = &user_referent, + }; + TEST_ASSERT_EQUAL(-UDPARD_ERROR_ARGUMENT, udpardTxInit(&tx, nullptr, 0, &memory)); + } + { + UdpardTx tx{}; + UdpardMemoryResource memory{ + .allocate = nullptr, + .free = &dummyAllocatorFree, + .user_reference = &user_referent, + }; + TEST_ASSERT_EQUAL(-UDPARD_ERROR_ARGUMENT, udpardTxInit(&tx, &node_id, 0, &memory)); + } + { + UdpardTx tx{}; + UdpardMemoryResource memory{ + .allocate = &dummyAllocatorAllocate, + .free = nullptr, + .user_reference = &user_referent, + }; + TEST_ASSERT_EQUAL(-UDPARD_ERROR_ARGUMENT, udpardTxInit(&tx, &node_id, 0, &memory)); + } + { + UdpardTx tx{}; + UdpardMemoryResource memory{ + .allocate = &dummyAllocatorAllocate, + .free = &dummyAllocatorFree, + .user_reference = &user_referent, + }; + TEST_ASSERT_EQUAL(0, udpardTxInit(&tx, &node_id, 0, &memory)); + TEST_ASSERT_EQUAL(&user_referent, tx.memory->user_reference); + TEST_ASSERT_EQUAL(UDPARD_MTU_DEFAULT, tx.mtu); + } +} + +void testPublish() +{ + InstrumentedAllocator alloc; + instrumentedAllocatorNew(&alloc); + const UdpardNodeID node_id = 1234; + // + UdpardTx tx{ + .local_node_id = &node_id, + .queue_capacity = 1U, + .mtu = UDPARD_MTU_DEFAULT, + .dscp_value_per_priority = {0, 1, 2, 3, 4, 5, 6, 7}, + .memory = &alloc.base, + .queue_size = 0, + .root = nullptr, + }; + std::monostate user_transfer_referent; + UdpardTransferID transfer_id = 0; + TEST_ASSERT_EQUAL(1, + udpardTxPublish(&tx, + 1234567890, + UdpardPriorityNominal, + 0x1432, + &transfer_id, + {.size = FleetingEvents.size(), .data = FleetingEvents.data()}, + &user_transfer_referent)); + TEST_ASSERT_EQUAL(1, transfer_id); + TEST_ASSERT_EQUAL(1, alloc.allocated_fragments); + TEST_ASSERT_EQUAL(1, tx.queue_size); + const auto* frame = udpardTxPeek(&tx); + std::cout << hexdump::hexdump(frame->datagram_payload.data, frame->datagram_payload.size) << "\n\n"; + TEST_ASSERT_NOT_EQUAL(nullptr, frame); + TEST_ASSERT_EQUAL(nullptr, frame->next_in_transfer); + TEST_ASSERT_EQUAL(1234567890, frame->deadline_usec); + TEST_ASSERT_EQUAL(4, frame->dscp); + TEST_ASSERT_EQUAL(0xEF00'1432UL, frame->destination.ip_address); + TEST_ASSERT_EQUAL(9382, frame->destination.udp_port); + TEST_ASSERT_EQUAL(&user_transfer_referent, frame->user_transfer_reference); + TEST_ASSERT_EQUAL(24 + FleetingEvents.size() + 4, frame->datagram_payload.size); + TEST_ASSERT_EQUAL(0, + std::memcmp(static_cast(frame->datagram_payload.data) + 24, + FleetingEvents.data(), + FleetingEvents.size())); + TEST_ASSERT_EQUAL(0, + std::memcmp(static_cast(frame->datagram_payload.data) + 24 + + FleetingEvents.size(), + FleetingEventsCRC.data(), + FleetingEventsCRC.size())); + udpardTxFree(tx.memory, udpardTxPop(&tx, frame)); + udpardTxFree(tx.memory, udpardTxPop(&tx, nullptr)); // No-op. + + // Out of queue; transfer-ID not incremented. + TEST_ASSERT_EQUAL(-UDPARD_ERROR_CAPACITY, + udpardTxPublish(&tx, + 1234567890, + UdpardPriorityNominal, + 0x1432, + &transfer_id, + {.size = tx.mtu * 2, .data = FleetingEvents.data()}, + nullptr)); + TEST_ASSERT_EQUAL(1, transfer_id); + + // Attempt to publish a multi-frame transfer with an anonymous local node. + { + auto tx_bad = tx; + const UdpardNodeID anonymous_node_id = 0xFFFFU; + tx_bad.queue_size = 1000; + tx_bad.mtu = 10; // Force multi-frame. + tx_bad.local_node_id = &anonymous_node_id; + TEST_ASSERT_EQUAL(-UDPARD_ERROR_ANONYMOUS, + udpardTxPublish(&tx_bad, + 1234567890, + UdpardPriorityNominal, + 0x1432, + &transfer_id, + {.size = FleetingEvents.size(), .data = FleetingEvents.data()}, + nullptr)); + TEST_ASSERT_EQUAL(1, transfer_id); + } + + // Invalid Tx. + TEST_ASSERT_EQUAL(-UDPARD_ERROR_ARGUMENT, + udpardTxPublish(nullptr, + 1234567890, + UdpardPriorityNominal, + 0x1432, + &transfer_id, + {.size = FleetingEvents.size(), .data = FleetingEvents.data()}, + nullptr)); + TEST_ASSERT_EQUAL(1, transfer_id); + // Invalid local node-ID. + { + auto tx_bad = tx; + tx_bad.local_node_id = nullptr; + TEST_ASSERT_EQUAL(-UDPARD_ERROR_ARGUMENT, + udpardTxPublish(&tx_bad, + 1234567890, + UdpardPriorityNominal, + 0x1432, + &transfer_id, + {.size = FleetingEvents.size(), .data = FleetingEvents.data()}, + nullptr)); + TEST_ASSERT_EQUAL(1, transfer_id); + } + // Invalid priority. + { + auto bad_priority = UdpardPriorityOptional; + TEST_ASSERT_EQUAL(-UDPARD_ERROR_ARGUMENT, + udpardTxPublish(&tx, + 1234567890, + (UdpardPriority) (bad_priority + 1), + 0x1432, + &transfer_id, + {.size = FleetingEvents.size(), .data = FleetingEvents.data()}, + nullptr)); + TEST_ASSERT_EQUAL(1, transfer_id); + } + // Invalid subject. + TEST_ASSERT_EQUAL(-UDPARD_ERROR_ARGUMENT, + udpardTxPublish(&tx, + 1234567890, + UdpardPriorityNominal, + 0xFFFFU, + &transfer_id, + {.size = FleetingEvents.size(), .data = FleetingEvents.data()}, + nullptr)); + TEST_ASSERT_EQUAL(1, transfer_id); + // Invalid transfer-ID pointer. + TEST_ASSERT_EQUAL(-UDPARD_ERROR_ARGUMENT, + udpardTxPublish(&tx, + 1234567890, + UdpardPriorityNominal, + 0x1432, + nullptr, + {.size = FleetingEvents.size(), .data = FleetingEvents.data()}, + nullptr)); + // Invalid payload pointer. + TEST_ASSERT_EQUAL(-UDPARD_ERROR_ARGUMENT, + udpardTxPublish(&tx, + 1234567890, + UdpardPriorityNominal, + 0x1432, + &transfer_id, + {.size = FleetingEvents.size(), .data = nullptr}, + nullptr)); + TEST_ASSERT_EQUAL(1, transfer_id); +} + +void testRequest() +{ + InstrumentedAllocator alloc; + instrumentedAllocatorNew(&alloc); + const UdpardNodeID node_id = 1234; + // + UdpardTx tx{ + .local_node_id = &node_id, + .queue_capacity = 1U, + .mtu = UDPARD_MTU_DEFAULT, + .dscp_value_per_priority = {0, 1, 2, 3, 4, 5, 6, 7}, + .memory = &alloc.base, + .queue_size = 0, + .root = nullptr, + }; + std::monostate user_transfer_referent; + UdpardTransferID transfer_id = 0; + TEST_ASSERT_EQUAL(1, + udpardTxRequest(&tx, + 1234567890, + UdpardPriorityNominal, + 0x123, + 0x1538, + &transfer_id, + {.size = FleetingEvents.size(), .data = FleetingEvents.data()}, + &user_transfer_referent)); + TEST_ASSERT_EQUAL(1, transfer_id); + TEST_ASSERT_EQUAL(1, alloc.allocated_fragments); + TEST_ASSERT_EQUAL(1, tx.queue_size); + const auto* frame = udpardTxPeek(&tx); + std::cout << hexdump::hexdump(frame->datagram_payload.data, frame->datagram_payload.size) << "\n\n"; + TEST_ASSERT_NOT_EQUAL(nullptr, frame); + TEST_ASSERT_EQUAL(nullptr, frame->next_in_transfer); + TEST_ASSERT_EQUAL(1234567890, frame->deadline_usec); + TEST_ASSERT_EQUAL(4, frame->dscp); + TEST_ASSERT_EQUAL(0xEF01'1538UL, frame->destination.ip_address); + TEST_ASSERT_EQUAL(9382, frame->destination.udp_port); + TEST_ASSERT_EQUAL(&user_transfer_referent, frame->user_transfer_reference); + TEST_ASSERT_EQUAL(24 + FleetingEvents.size() + 4, frame->datagram_payload.size); + TEST_ASSERT_EQUAL(0, + std::memcmp(static_cast(frame->datagram_payload.data) + 24, + FleetingEvents.data(), + FleetingEvents.size())); + TEST_ASSERT_EQUAL(0, + std::memcmp(static_cast(frame->datagram_payload.data) + 24 + + FleetingEvents.size(), + FleetingEventsCRC.data(), + FleetingEventsCRC.size())); + udpardTxFree(tx.memory, udpardTxPop(&tx, frame)); + udpardTxFree(tx.memory, udpardTxPop(&tx, nullptr)); // No-op. + + // Out of queue; transfer-ID not incremented. + TEST_ASSERT_EQUAL(-UDPARD_ERROR_CAPACITY, + udpardTxRequest(&tx, + 1234567890, + UdpardPriorityNominal, + 0x123, + 0x1538, + &transfer_id, + {.size = tx.mtu * 2, .data = FleetingEvents.data()}, + nullptr)); + TEST_ASSERT_EQUAL(1, transfer_id); + + // Attempt to send a service transfer from an anonymous node. + { + auto tx_bad = tx; + const UdpardNodeID anonymous_node_id = 0xFFFFU; + tx_bad.queue_size = 1000; + tx_bad.mtu = 10; // Force multi-frame. + tx_bad.local_node_id = &anonymous_node_id; + TEST_ASSERT_EQUAL(-UDPARD_ERROR_ANONYMOUS, + udpardTxRequest(&tx_bad, + 1234567890, + UdpardPriorityNominal, + 0x123, + 0x1538, + &transfer_id, + {.size = FleetingEvents.size(), .data = FleetingEvents.data()}, + nullptr)); + TEST_ASSERT_EQUAL(1, transfer_id); + } + + // Invalid Tx. + TEST_ASSERT_EQUAL(-UDPARD_ERROR_ARGUMENT, + udpardTxRequest(nullptr, + 1234567890, + UdpardPriorityNominal, + 0x123, + 0x1538, + &transfer_id, + {.size = FleetingEvents.size(), .data = FleetingEvents.data()}, + nullptr)); + TEST_ASSERT_EQUAL(1, transfer_id); + // Invalid local node-ID. + { + auto tx_bad = tx; + tx_bad.local_node_id = nullptr; + TEST_ASSERT_EQUAL(-UDPARD_ERROR_ARGUMENT, + udpardTxRequest(&tx_bad, + 1234567890, + UdpardPriorityNominal, + 0x123, + 0x1538, + &transfer_id, + {.size = FleetingEvents.size(), .data = FleetingEvents.data()}, + nullptr)); + TEST_ASSERT_EQUAL(1, transfer_id); + } + // Invalid priority. + { + auto bad_priority = UdpardPriorityOptional; + TEST_ASSERT_EQUAL(-UDPARD_ERROR_ARGUMENT, + udpardTxRequest(&tx, + 1234567890, + (UdpardPriority) (bad_priority + 1), + 0x123, + 0x1538, + &transfer_id, + {.size = FleetingEvents.size(), .data = FleetingEvents.data()}, + nullptr)); + TEST_ASSERT_EQUAL(1, transfer_id); + } + // Invalid remote node-ID. + TEST_ASSERT_EQUAL(-UDPARD_ERROR_ARGUMENT, + udpardTxRequest(&tx, + 1234567890, + UdpardPriorityNominal, + 0x123, + 0xFFFF, + &transfer_id, + {.size = FleetingEvents.size(), .data = FleetingEvents.data()}, + nullptr)); + TEST_ASSERT_EQUAL(1, transfer_id); + // Invalid service-ID. + TEST_ASSERT_EQUAL(-UDPARD_ERROR_ARGUMENT, + udpardTxRequest(&tx, + 1234567890, + UdpardPriorityNominal, + 0xFFFFU, + 0x1538, + &transfer_id, + {.size = FleetingEvents.size(), .data = FleetingEvents.data()}, + nullptr)); + TEST_ASSERT_EQUAL(1, transfer_id); + // Invalid transfer-ID pointer. + TEST_ASSERT_EQUAL(-UDPARD_ERROR_ARGUMENT, + udpardTxRequest(&tx, + 1234567890, + UdpardPriorityNominal, + 0x123, + 0x1538, + nullptr, + {.size = FleetingEvents.size(), .data = FleetingEvents.data()}, + nullptr)); + // Invalid payload pointer. + TEST_ASSERT_EQUAL(-UDPARD_ERROR_ARGUMENT, + udpardTxRequest(&tx, + 1234567890, + UdpardPriorityNominal, + 0x123, + 0x1538, + &transfer_id, + {.size = FleetingEvents.size(), .data = nullptr}, + nullptr)); + TEST_ASSERT_EQUAL(1, transfer_id); +} + +void testRespond() +{ + InstrumentedAllocator alloc; + instrumentedAllocatorNew(&alloc); + const UdpardNodeID node_id = 1234; + // + UdpardTx tx{ + .local_node_id = &node_id, + .queue_capacity = 1U, + .mtu = UDPARD_MTU_DEFAULT, + .dscp_value_per_priority = {0, 1, 2, 3, 4, 5, 6, 7}, + .memory = &alloc.base, + .queue_size = 0, + .root = nullptr, + }; + std::monostate user_transfer_referent; + TEST_ASSERT_EQUAL(1, + udpardTxRespond(&tx, + 1234567890, + UdpardPriorityNominal, + 0x123, + 0x1538, + 9876543210, + {.size = FleetingEvents.size(), .data = FleetingEvents.data()}, + &user_transfer_referent)); + TEST_ASSERT_EQUAL(1, alloc.allocated_fragments); + TEST_ASSERT_EQUAL(1, tx.queue_size); + const auto* frame = udpardTxPeek(&tx); + std::cout << hexdump::hexdump(frame->datagram_payload.data, frame->datagram_payload.size) << "\n\n"; + TEST_ASSERT_NOT_EQUAL(nullptr, frame); + TEST_ASSERT_EQUAL(nullptr, frame->next_in_transfer); + TEST_ASSERT_EQUAL(1234567890, frame->deadline_usec); + TEST_ASSERT_EQUAL(4, frame->dscp); + TEST_ASSERT_EQUAL(0xEF01'1538UL, frame->destination.ip_address); + TEST_ASSERT_EQUAL(9382, frame->destination.udp_port); + TEST_ASSERT_EQUAL(&user_transfer_referent, frame->user_transfer_reference); + TEST_ASSERT_EQUAL(24 + FleetingEvents.size() + 4, frame->datagram_payload.size); + TEST_ASSERT_EQUAL(0, + std::memcmp(static_cast(frame->datagram_payload.data) + 24, + FleetingEvents.data(), + FleetingEvents.size())); + TEST_ASSERT_EQUAL(0, + std::memcmp(static_cast(frame->datagram_payload.data) + 24 + + FleetingEvents.size(), + FleetingEventsCRC.data(), + FleetingEventsCRC.size())); + udpardTxFree(tx.memory, udpardTxPop(&tx, frame)); + udpardTxFree(tx.memory, udpardTxPop(&tx, nullptr)); // No-op. + + // Out of queue; transfer-ID not incremented. + TEST_ASSERT_EQUAL(-UDPARD_ERROR_CAPACITY, + udpardTxRespond(&tx, + 1234567890, + UdpardPriorityNominal, + 0x123, + 0x1538, + 0, + {.size = tx.mtu * 2, .data = FleetingEvents.data()}, + nullptr)); + + // Attempt to send a service transfer from an anonymous node. + { + auto tx_bad = tx; + const UdpardNodeID anonymous_node_id = 0xFFFFU; + tx_bad.queue_size = 1000; + tx_bad.mtu = 10; // Force multi-frame. + tx_bad.local_node_id = &anonymous_node_id; + TEST_ASSERT_EQUAL(-UDPARD_ERROR_ANONYMOUS, + udpardTxRespond(&tx_bad, + 1234567890, + UdpardPriorityNominal, + 0x123, + 0x1538, + 0, + {.size = FleetingEvents.size(), .data = FleetingEvents.data()}, + nullptr)); + } + + // Invalid Tx. + TEST_ASSERT_EQUAL(-UDPARD_ERROR_ARGUMENT, + udpardTxRespond(nullptr, + 1234567890, + UdpardPriorityNominal, + 0x123, + 0x1538, + 0, + {.size = FleetingEvents.size(), .data = FleetingEvents.data()}, + nullptr)); + // Invalid local node-ID. + { + auto tx_bad = tx; + tx_bad.local_node_id = nullptr; + TEST_ASSERT_EQUAL(-UDPARD_ERROR_ARGUMENT, + udpardTxRespond(&tx_bad, + 1234567890, + UdpardPriorityNominal, + 0x123, + 0x1538, + 0, + {.size = FleetingEvents.size(), .data = FleetingEvents.data()}, + nullptr)); + } + // Invalid priority. + { + auto bad_priority = UdpardPriorityOptional; + TEST_ASSERT_EQUAL(-UDPARD_ERROR_ARGUMENT, + udpardTxRespond(&tx, + 1234567890, + (UdpardPriority) (bad_priority + 1), + 0x123, + 0x1538, + 0, + {.size = FleetingEvents.size(), .data = FleetingEvents.data()}, + nullptr)); + } + // Invalid remote node-ID. + TEST_ASSERT_EQUAL(-UDPARD_ERROR_ARGUMENT, + udpardTxRespond(&tx, + 1234567890, + UdpardPriorityNominal, + 0x123, + 0xFFFF, + 0, + {.size = FleetingEvents.size(), .data = FleetingEvents.data()}, + nullptr)); + // Invalid service-ID. + TEST_ASSERT_EQUAL(-UDPARD_ERROR_ARGUMENT, + udpardTxRespond(&tx, + 1234567890, + UdpardPriorityNominal, + 0xFFFFU, + 0x1538, + 0, + {.size = FleetingEvents.size(), .data = FleetingEvents.data()}, + nullptr)); + // Invalid payload pointer. + TEST_ASSERT_EQUAL(-UDPARD_ERROR_ARGUMENT, + udpardTxRespond(&tx, + 1234567890, + UdpardPriorityNominal, + 0x123, + 0x1538, + 0, + {.size = FleetingEvents.size(), .data = nullptr}, + nullptr)); +} + +void testPeekPopFreeNULL() // Just make sure we don't crash. +{ + TEST_ASSERT_EQUAL(nullptr, udpardTxPeek(nullptr)); + TEST_ASSERT_EQUAL(nullptr, udpardTxPop(nullptr, nullptr)); + udpardTxFree(nullptr, nullptr); +} + +} // namespace + +void setUp() {} + +void tearDown() {} + +int main() +{ + UNITY_BEGIN(); + RUN_TEST(testInit); + RUN_TEST(testPublish); + RUN_TEST(testRequest); + RUN_TEST(testRespond); + RUN_TEST(testPeekPopFreeNULL); + return UNITY_END(); +} diff --git a/tests/test_private_crc.cpp b/tests/test_private_crc.cpp deleted file mode 100644 index c14ae92..0000000 --- a/tests/test_private_crc.cpp +++ /dev/null @@ -1,31 +0,0 @@ -/// This software is distributed under the terms of the MIT License. -/// Copyright (C) OpenCyphal Development Team -/// Copyright Amazon.com Inc. or its affiliates. -/// SPDX-License-Identifier: MIT - -#include "exposed.hpp" -#include - -TEST(CRC, Header) -{ - using exposed::headerCRCCompute; - ASSERT_EQ(0x29B1U, headerCRCCompute(9, "123456789")); -} - -TEST(CRC, Transfer) -{ - using exposed::transferCRCAdd; - constexpr std::uint32_t OutputXOR = 0xFFFFFFFFU; - auto crc = transferCRCAdd(0xFFFFFFFFU, 3, "123"); - crc = transferCRCAdd(crc, 6, "456789"); - ASSERT_EQ(0x1CF96D7CUL, crc); - ASSERT_EQ(0xE3069283UL, crc ^ OutputXOR); - crc = transferCRCAdd(crc, - 4, - "\x83" // Least significant byte first. - "\x92" - "\x06" - "\xE3"); - ASSERT_EQ(0xB798B438UL, crc); - ASSERT_EQ(0x48674BC7UL, crc ^ OutputXOR); -} diff --git a/tests/test_private_tx.cpp b/tests/test_private_tx.cpp deleted file mode 100644 index 7000c5d..0000000 --- a/tests/test_private_tx.cpp +++ /dev/null @@ -1,875 +0,0 @@ -/// This software is distributed under the terms of the MIT License. -/// Copyright (C) OpenCyphal Development Team -/// Copyright Amazon.com Inc. or its affiliates. -/// SPDX-License-Identifier: MIT - -#include "exposed.hpp" -#include "helpers.hpp" -#include "hexdump.hpp" -#include -#include - -namespace -{ -using exposed::HeaderSize; -using exposed::TransferMetadata; -using exposed::TxItem; -using exposed::txSerializeHeader; -using exposed::txMakeChain; -using exposed::txPush; - -// >>> from pycyphal.transport.commons.crc import CRC32C -// >>> list(CRC32C.new(data).value_as_bytes) -constexpr std::string_view EtherealStrength = - "All was silent except for the howl of the wind against the antenna. Ye watched as the remaining birds in the " - "flock gradually settled back into the forest. She stared at the antenna and thought it looked like an enormous " - "hand stretched open toward the sky, possessing an ethereal strength."; -constexpr std::array EtherealStrengthCRC{{209, 88, 130, 43}}; - -constexpr std::string_view DetailOfTheCosmos = - "For us, the dark forest state is all-important, but it's just a detail of the cosmos."; -constexpr std::array DetailOfTheCosmosCRC{{125, 113, 207, 171}}; - -constexpr std::string_view InterstellarWar = "You have not seen what a true interstellar war is like."; -constexpr std::array InterstellarWarCRC{{102, 217, 109, 188}}; - -auto makeHeader(const TransferMetadata meta, const std::uint32_t frame_index, const bool end_of_transfer) -{ - std::array buffer{}; - (void) txSerializeHeader(buffer.data(), meta, frame_index, end_of_transfer); - return buffer; -} -} // namespace - -// Generate reference data using PyCyphal: -// -// >>> from pycyphal.transport.udp import UDPFrame -// >>> from pycyphal.transport import Priority, MessageDataSpecifier -// >>> frame = UDPFrame(priority=Priority.FAST, transfer_id=0xbadc0ffee0ddf00d, index=12345, end_of_transfer=False, -// payload=memoryview(b''), source_node_id=2345, destination_node_id=5432, -// data_specifier=MessageDataSpecifier(7654), user_data=0) -// >>> list(frame.compile_header_and_payload()[0]) -// [1, 2, 41, 9, 56, 21, 230, 29, 13, 240, 221, 224, 254, 15, 220, 186, 57, 48, 0, 0, 0, 0, 224, 60] -TEST(TxPrivate, SerializeHeader) -{ - using HeaderBuffer = std::array; - { - HeaderBuffer buffer{}; - ASSERT_EQ(buffer.end(), - txSerializeHeader(buffer.data(), - { - .priority = UdpardPriorityFast, - .src_node_id = 2345, - .dst_node_id = 5432, - .data_specifier = 7654, - .transfer_id = 0xBADC'0FFE'E0DD'F00dULL, - }, - 12345, - false)); - const HeaderBuffer ref{ - {1, 2, 41, 9, 56, 21, 230, 29, 13, 240, 221, 224, 254, 15, 220, 186, 57, 48, 0, 0, 0, 0, 224, 60}}; - ASSERT_EQ(ref, buffer); - } - { - HeaderBuffer buffer{}; - ASSERT_EQ(buffer.end(), - txSerializeHeader(buffer.data(), - { - .priority = UdpardPriorityLow, - .src_node_id = 0xFEDC, - .dst_node_id = 0xBA98, - .data_specifier = 1234, - .transfer_id = 0x0BAD'C0DE'0BAD'C0DEULL, - }, - 0x7FFF, - true)); - const HeaderBuffer ref{ - {1, 5, 220, 254, 152, 186, 210, 4, 222, 192, 173, 11, 222, 192, 173, 11, 255, 127, 0, 128, 0, 0, 229, 4}}; - ASSERT_EQ(ref, buffer); - } -} - -TEST(TxPrivate, MakeChainEmpty) -{ - helpers::TestAllocator alloc; - std::monostate user_transfer_referent; - const TransferMetadata meta{ - .priority = UdpardPriorityFast, - .src_node_id = 1234, - .dst_node_id = 2345, - .data_specifier = 5432, - .transfer_id = 0xBADC'0FFE'E0DD'F00DULL, - }; - const auto chain = txMakeChain(&alloc, - std::array{{11, 22, 33, 44, 55, 66, 77, 88}}.data(), - 30, - 1234567890, - meta, - UdpardUDPIPEndpoint{.ip_address = 0x0A0B'0C0DU, .udp_port = 0x1234}, - UdpardConstPayload{.size = 0, .data = ""}, - &user_transfer_referent); - ASSERT_EQ(1, alloc.getNumAllocatedFragments()); - ASSERT_EQ(sizeof(TxItem) + HeaderSize + 4, alloc.getTotalAllocatedAmount()); - ASSERT_EQ(1, chain.count); - std::cout << hexdump::hexdump(chain.head->datagram_payload.data, chain.head->datagram_payload.size) << "\n\n"; - ASSERT_EQ(chain.head, chain.tail); - ASSERT_EQ(nullptr, chain.head->next_in_transfer); - ASSERT_EQ(1234567890, chain.head->deadline_usec); - ASSERT_EQ(33, chain.head->dscp); - ASSERT_EQ(0x0A0B'0C0DU, chain.head->destination.ip_address); - ASSERT_EQ(0x1234, chain.head->destination.udp_port); - ASSERT_EQ(HeaderSize + 4, chain.head->datagram_payload.size); - ASSERT_EQ(0, std::memcmp(makeHeader(meta, 0, true).data(), chain.head->datagram_payload.data, HeaderSize)); - ASSERT_EQ(0, - std::memcmp("\x00\x00\x00\x00", // CRC of the empty transfer. - static_cast(chain.head->datagram_payload.data) + HeaderSize, - 4)); - ASSERT_EQ(&user_transfer_referent, chain.head->user_transfer_reference); -} - -TEST(TxPrivate, MakeChainSingleMaxMTU) -{ - helpers::TestAllocator alloc; - std::monostate user_transfer_referent; - const TransferMetadata meta{ - .priority = UdpardPrioritySlow, - .src_node_id = 4321, - .dst_node_id = 5432, - .data_specifier = 7766, - .transfer_id = 0x0123'4567'89AB'CDEFULL, - }; - const auto chain = - txMakeChain(&alloc, - std::array{{11, 22, 33, 44, 55, 66, 77, 88}}.data(), - DetailOfTheCosmos.size() + DetailOfTheCosmosCRC.size(), - 1234567890, - meta, - UdpardUDPIPEndpoint{.ip_address = 0x0A0B'0C00U, .udp_port = 7474}, - UdpardConstPayload{.size = DetailOfTheCosmos.size(), .data = DetailOfTheCosmos.data()}, - &user_transfer_referent); - ASSERT_EQ(1, alloc.getNumAllocatedFragments()); - ASSERT_EQ(sizeof(TxItem) + HeaderSize + DetailOfTheCosmos.size() + DetailOfTheCosmosCRC.size(), - alloc.getTotalAllocatedAmount()); - ASSERT_EQ(1, chain.count); - std::cout << hexdump::hexdump(chain.head->datagram_payload.data, chain.head->datagram_payload.size) << "\n\n"; - ASSERT_EQ(chain.head, chain.tail); - ASSERT_EQ(nullptr, chain.head->next_in_transfer); - ASSERT_EQ(1234567890, chain.head->deadline_usec); - ASSERT_EQ(77, chain.head->dscp); - ASSERT_EQ(0x0A0B'0C00U, chain.head->destination.ip_address); - ASSERT_EQ(7474, chain.head->destination.udp_port); - ASSERT_EQ(HeaderSize + DetailOfTheCosmos.size() + DetailOfTheCosmosCRC.size(), chain.head->datagram_payload.size); - ASSERT_EQ(0, std::memcmp(makeHeader(meta, 0, true).data(), chain.head->datagram_payload.data, HeaderSize)); - ASSERT_EQ(0, - std::memcmp(DetailOfTheCosmos.data(), - static_cast(chain.head->datagram_payload.data) + HeaderSize, - DetailOfTheCosmos.size())); - ASSERT_EQ(0, - std::memcmp(DetailOfTheCosmosCRC.data(), - static_cast(chain.head->datagram_payload.data) + HeaderSize + - DetailOfTheCosmos.size(), - DetailOfTheCosmosCRC.size())); - ASSERT_EQ(&user_transfer_referent, chain.head->user_transfer_reference); -} - -TEST(TxPrivate, MakeChainThreeFrames) -{ - helpers::TestAllocator alloc; - std::monostate user_transfer_referent; - const TransferMetadata meta{ - .priority = UdpardPriorityNominal, - .src_node_id = 4321, - .dst_node_id = 5432, - .data_specifier = 7766, - .transfer_id = 0x0123'4567'89AB'CDEFULL, - }; - const auto mtu = (EtherealStrength.size() + 4U + 3U) / 3U; // Force payload split into three frames. - const auto chain = txMakeChain(&alloc, - std::array{{11, 22, 33, 44, 55, 66, 77, 88}}.data(), - mtu, - 223574680, - meta, - UdpardUDPIPEndpoint{.ip_address = 0xBABA'DEDAU, .udp_port = 0xD0ED}, - UdpardConstPayload{.size = EtherealStrength.size(), .data = EtherealStrength.data()}, - &user_transfer_referent); - ASSERT_EQ(3, alloc.getNumAllocatedFragments()); - ASSERT_EQ(3 * (sizeof(TxItem) + HeaderSize) + EtherealStrength.size() + 4U, alloc.getTotalAllocatedAmount()); - ASSERT_EQ(3, chain.count); - const auto* const first = chain.head; - ASSERT_NE(nullptr, first); - const auto* const second = first->next_in_transfer; - ASSERT_NE(nullptr, second); - const auto* const third = second->next_in_transfer; - ASSERT_NE(nullptr, third); - ASSERT_EQ(nullptr, third->next_in_transfer); - ASSERT_EQ(chain.tail, third); - - // FIRST FRAME -- contains the first part of the payload. - std::cout << hexdump::hexdump(first->datagram_payload.data, first->datagram_payload.size) << "\n\n"; - ASSERT_EQ(223574680, first->deadline_usec); - ASSERT_EQ(55, first->dscp); - ASSERT_EQ(0xBABA'DEDAU, first->destination.ip_address); - ASSERT_EQ(0xD0ED, first->destination.udp_port); - ASSERT_EQ(HeaderSize + mtu, first->datagram_payload.size); - ASSERT_EQ(0, std::memcmp(makeHeader(meta, 0, false).data(), first->datagram_payload.data, HeaderSize)); - ASSERT_EQ(0, - std::memcmp(EtherealStrength.data(), - static_cast(first->datagram_payload.data) + HeaderSize, - mtu)); - ASSERT_EQ(&user_transfer_referent, first->user_transfer_reference); - - // SECOND FRAME -- contains the second part of the payload. - std::cout << hexdump::hexdump(second->datagram_payload.data, second->datagram_payload.size) << "\n\n"; - ASSERT_EQ(223574680, second->deadline_usec); - ASSERT_EQ(55, second->dscp); - ASSERT_EQ(0xBABA'DEDAU, second->destination.ip_address); - ASSERT_EQ(0xD0ED, second->destination.udp_port); - ASSERT_EQ(HeaderSize + mtu, second->datagram_payload.size); - ASSERT_EQ(0, std::memcmp(makeHeader(meta, 1, false).data(), second->datagram_payload.data, HeaderSize)); - ASSERT_EQ(0, - std::memcmp(EtherealStrength.data() + mtu, - static_cast(second->datagram_payload.data) + HeaderSize, - mtu)); - ASSERT_EQ(&user_transfer_referent, second->user_transfer_reference); - - // THIRD FRAME -- contains the third part of the payload and the CRC at the end. - std::cout << hexdump::hexdump(third->datagram_payload.data, third->datagram_payload.size) << "\n\n"; - ASSERT_EQ(223574680, third->deadline_usec); - ASSERT_EQ(55, third->dscp); - ASSERT_EQ(0xBABA'DEDAU, third->destination.ip_address); - ASSERT_EQ(0xD0ED, third->destination.udp_port); - const auto third_payload_size = EtherealStrength.size() - 2 * mtu; - ASSERT_EQ(HeaderSize + third_payload_size + 4U, third->datagram_payload.size); - ASSERT_EQ(0, std::memcmp(makeHeader(meta, 2, true).data(), third->datagram_payload.data, HeaderSize)); - ASSERT_EQ(0, - std::memcmp(EtherealStrength.data() + 2 * mtu, - static_cast(third->datagram_payload.data) + HeaderSize, - third_payload_size)); - ASSERT_EQ(0, - std::memcmp(EtherealStrengthCRC.data(), - static_cast(third->datagram_payload.data) + HeaderSize + third_payload_size, - EtherealStrengthCRC.size())); - ASSERT_EQ(&user_transfer_referent, third->user_transfer_reference); -} - -TEST(TxPrivate, MakeChainCRCSpill1) -{ - helpers::TestAllocator alloc; - std::monostate user_transfer_referent; - const TransferMetadata meta{ - .priority = UdpardPriorityNominal, - .src_node_id = 4321, - .dst_node_id = 5432, - .data_specifier = 7766, - .transfer_id = 0x0123'4567'89AB'CDEFULL, - }; - const auto mtu = InterstellarWar.size() + 3U; - const auto chain = txMakeChain(&alloc, - std::array{{11, 22, 33, 44, 55, 66, 77, 88}}.data(), - mtu, - 223574680, - meta, - UdpardUDPIPEndpoint{.ip_address = 0xBABA'DEDAU, .udp_port = 0xD0ED}, - UdpardConstPayload{.size = InterstellarWar.size(), .data = InterstellarWar.data()}, - &user_transfer_referent); - ASSERT_EQ(2, alloc.getNumAllocatedFragments()); - ASSERT_EQ(2 * (sizeof(TxItem) + HeaderSize) + InterstellarWar.size() + 4U, alloc.getTotalAllocatedAmount()); - ASSERT_EQ(2, chain.count); - ASSERT_NE(chain.head, chain.tail); - ASSERT_EQ(chain.tail, chain.head->next_in_transfer); - ASSERT_EQ(nullptr, chain.tail->next_in_transfer); - - // FIRST FRAME -- contains the payload and the first three bytes of the CRC. - std::cout << hexdump::hexdump(chain.head->datagram_payload.data, chain.head->datagram_payload.size) << "\n\n"; - ASSERT_EQ(223574680, chain.head->deadline_usec); - ASSERT_EQ(55, chain.head->dscp); - ASSERT_EQ(0xBABA'DEDAU, chain.head->destination.ip_address); - ASSERT_EQ(0xD0ED, chain.head->destination.udp_port); - ASSERT_EQ(HeaderSize + mtu, chain.head->datagram_payload.size); - ASSERT_EQ(0, std::memcmp(makeHeader(meta, 0, false).data(), chain.head->datagram_payload.data, HeaderSize)); - ASSERT_EQ(0, - std::memcmp(InterstellarWar.data(), - static_cast(chain.head->datagram_payload.data) + HeaderSize, - InterstellarWar.size())); - ASSERT_EQ(0, - std::memcmp(InterstellarWarCRC.data(), - static_cast(chain.head->datagram_payload.data) + HeaderSize + - InterstellarWar.size(), - 3U)); - ASSERT_EQ(&user_transfer_referent, chain.head->user_transfer_reference); - - // SECOND FRAME -- contains the last byte of the CRC. - std::cout << hexdump::hexdump(chain.tail->datagram_payload.data, chain.tail->datagram_payload.size) << "\n\n"; - ASSERT_EQ(223574680, chain.tail->deadline_usec); - ASSERT_EQ(55, chain.tail->dscp); - ASSERT_EQ(0xBABA'DEDAU, chain.tail->destination.ip_address); - ASSERT_EQ(0xD0ED, chain.tail->destination.udp_port); - ASSERT_EQ(HeaderSize + 1U, chain.tail->datagram_payload.size); - ASSERT_EQ(0, std::memcmp(makeHeader(meta, 1, true).data(), chain.tail->datagram_payload.data, HeaderSize)); - ASSERT_EQ(0, - std::memcmp(InterstellarWarCRC.data() + 3U, - static_cast(chain.tail->datagram_payload.data) + HeaderSize, - 1U)); - ASSERT_EQ(&user_transfer_referent, chain.tail->user_transfer_reference); -} - -TEST(TxPrivate, MakeChainCRCSpill2) -{ - helpers::TestAllocator alloc; - std::monostate user_transfer_referent; - const TransferMetadata meta{ - .priority = UdpardPriorityNominal, - .src_node_id = 4321, - .dst_node_id = 5432, - .data_specifier = 7766, - .transfer_id = 0x0123'4567'89AB'CDEFULL, - }; - const auto mtu = InterstellarWar.size() + 2U; - const auto chain = txMakeChain(&alloc, - std::array{{11, 22, 33, 44, 55, 66, 77, 88}}.data(), - mtu, - 223574680, - meta, - UdpardUDPIPEndpoint{.ip_address = 0xBABA'DEDAU, .udp_port = 0xD0ED}, - UdpardConstPayload{.size = InterstellarWar.size(), .data = InterstellarWar.data()}, - &user_transfer_referent); - ASSERT_EQ(2, alloc.getNumAllocatedFragments()); - ASSERT_EQ(2 * (sizeof(TxItem) + HeaderSize) + InterstellarWar.size() + 4U, alloc.getTotalAllocatedAmount()); - ASSERT_EQ(2, chain.count); - ASSERT_NE(chain.head, chain.tail); - ASSERT_EQ(chain.tail, chain.head->next_in_transfer); - ASSERT_EQ(nullptr, chain.tail->next_in_transfer); - - // FIRST FRAME -- contains the payload and the first two bytes of the CRC. - std::cout << hexdump::hexdump(chain.head->datagram_payload.data, chain.head->datagram_payload.size) << "\n\n"; - ASSERT_EQ(223574680, chain.head->deadline_usec); - ASSERT_EQ(55, chain.head->dscp); - ASSERT_EQ(0xBABA'DEDAU, chain.head->destination.ip_address); - ASSERT_EQ(0xD0ED, chain.head->destination.udp_port); - ASSERT_EQ(HeaderSize + mtu, chain.head->datagram_payload.size); - ASSERT_EQ(0, std::memcmp(makeHeader(meta, 0, false).data(), chain.head->datagram_payload.data, HeaderSize)); - ASSERT_EQ(0, - std::memcmp(InterstellarWar.data(), - static_cast(chain.head->datagram_payload.data) + HeaderSize, - InterstellarWar.size())); - ASSERT_EQ(0, - std::memcmp(InterstellarWarCRC.data(), - static_cast(chain.head->datagram_payload.data) + HeaderSize + - InterstellarWar.size(), - 2U)); - ASSERT_EQ(&user_transfer_referent, chain.head->user_transfer_reference); - - // SECOND FRAME -- contains the last two bytes of the CRC. - std::cout << hexdump::hexdump(chain.tail->datagram_payload.data, chain.tail->datagram_payload.size) << "\n\n"; - ASSERT_EQ(223574680, chain.tail->deadline_usec); - ASSERT_EQ(55, chain.tail->dscp); - ASSERT_EQ(0xBABA'DEDAU, chain.tail->destination.ip_address); - ASSERT_EQ(0xD0ED, chain.tail->destination.udp_port); - ASSERT_EQ(HeaderSize + 2U, chain.tail->datagram_payload.size); - ASSERT_EQ(0, std::memcmp(makeHeader(meta, 1, true).data(), chain.tail->datagram_payload.data, HeaderSize)); - ASSERT_EQ(0, - std::memcmp(InterstellarWarCRC.data() + 2U, - static_cast(chain.tail->datagram_payload.data) + HeaderSize, - 2U)); - ASSERT_EQ(&user_transfer_referent, chain.tail->user_transfer_reference); -} - -TEST(TxPrivate, MakeChainCRCSpill3) -{ - helpers::TestAllocator alloc; - std::monostate user_transfer_referent; - const TransferMetadata meta{ - .priority = UdpardPriorityNominal, - .src_node_id = 4321, - .dst_node_id = 5432, - .data_specifier = 7766, - .transfer_id = 0x0123'4567'89AB'CDEFULL, - }; - const auto mtu = InterstellarWar.size() + 1U; - const auto chain = txMakeChain(&alloc, - std::array{{11, 22, 33, 44, 55, 66, 77, 88}}.data(), - mtu, - 223574680, - meta, - UdpardUDPIPEndpoint{.ip_address = 0xBABA'DEDAU, .udp_port = 0xD0ED}, - UdpardConstPayload{.size = InterstellarWar.size(), .data = InterstellarWar.data()}, - &user_transfer_referent); - ASSERT_EQ(2, alloc.getNumAllocatedFragments()); - ASSERT_EQ(2 * (sizeof(TxItem) + HeaderSize) + InterstellarWar.size() + 4U, alloc.getTotalAllocatedAmount()); - ASSERT_EQ(2, chain.count); - ASSERT_NE(chain.head, chain.tail); - ASSERT_EQ(chain.tail, chain.head->next_in_transfer); - ASSERT_EQ(nullptr, chain.tail->next_in_transfer); - - // FIRST FRAME -- contains the payload and the first byte of the CRC. - std::cout << hexdump::hexdump(chain.head->datagram_payload.data, chain.head->datagram_payload.size) << "\n\n"; - ASSERT_EQ(223574680, chain.head->deadline_usec); - ASSERT_EQ(55, chain.head->dscp); - ASSERT_EQ(0xBABA'DEDAU, chain.head->destination.ip_address); - ASSERT_EQ(0xD0ED, chain.head->destination.udp_port); - ASSERT_EQ(HeaderSize + mtu, chain.head->datagram_payload.size); - ASSERT_EQ(0, std::memcmp(makeHeader(meta, 0, false).data(), chain.head->datagram_payload.data, HeaderSize)); - ASSERT_EQ(0, - std::memcmp(InterstellarWar.data(), - static_cast(chain.head->datagram_payload.data) + HeaderSize, - InterstellarWar.size())); - ASSERT_EQ(0, - std::memcmp(InterstellarWarCRC.data(), - static_cast(chain.head->datagram_payload.data) + HeaderSize + - InterstellarWar.size(), - 1U)); - ASSERT_EQ(&user_transfer_referent, chain.head->user_transfer_reference); - - // SECOND FRAME -- contains the last three bytes of the CRC. - std::cout << hexdump::hexdump(chain.tail->datagram_payload.data, chain.tail->datagram_payload.size) << "\n\n"; - ASSERT_EQ(223574680, chain.tail->deadline_usec); - ASSERT_EQ(55, chain.tail->dscp); - ASSERT_EQ(0xBABA'DEDAU, chain.tail->destination.ip_address); - ASSERT_EQ(0xD0ED, chain.tail->destination.udp_port); - ASSERT_EQ(HeaderSize + 3U, chain.tail->datagram_payload.size); - ASSERT_EQ(0, std::memcmp(makeHeader(meta, 1, true).data(), chain.tail->datagram_payload.data, HeaderSize)); - ASSERT_EQ(0, - std::memcmp(InterstellarWarCRC.data() + 1U, - static_cast(chain.tail->datagram_payload.data) + HeaderSize, - 3U)); - ASSERT_EQ(&user_transfer_referent, chain.tail->user_transfer_reference); -} - -TEST(TxPrivate, MakeChainCRCSpillFull) -{ - helpers::TestAllocator alloc; - std::monostate user_transfer_referent; - const TransferMetadata meta{ - .priority = UdpardPriorityNominal, - .src_node_id = 4321, - .dst_node_id = 5432, - .data_specifier = 7766, - .transfer_id = 0x0123'4567'89AB'CDEFULL, - }; - const auto mtu = InterstellarWar.size(); - const auto chain = txMakeChain(&alloc, - std::array{{11, 22, 33, 44, 55, 66, 77, 88}}.data(), - mtu, - 223574680, - meta, - UdpardUDPIPEndpoint{.ip_address = 0xBABA'DEDAU, .udp_port = 0xD0ED}, - UdpardConstPayload{.size = InterstellarWar.size(), .data = InterstellarWar.data()}, - &user_transfer_referent); - ASSERT_EQ(2, alloc.getNumAllocatedFragments()); - ASSERT_EQ(2 * (sizeof(TxItem) + HeaderSize) + InterstellarWar.size() + 4U, alloc.getTotalAllocatedAmount()); - ASSERT_EQ(2, chain.count); - ASSERT_NE(chain.head, chain.tail); - ASSERT_EQ(chain.tail, chain.head->next_in_transfer); - ASSERT_EQ(nullptr, chain.tail->next_in_transfer); - - // FIRST FRAME -- contains the payload only. - std::cout << hexdump::hexdump(chain.head->datagram_payload.data, chain.head->datagram_payload.size) << "\n\n"; - ASSERT_EQ(223574680, chain.head->deadline_usec); - ASSERT_EQ(55, chain.head->dscp); - ASSERT_EQ(0xBABA'DEDAU, chain.head->destination.ip_address); - ASSERT_EQ(0xD0ED, chain.head->destination.udp_port); - ASSERT_EQ(HeaderSize + mtu, chain.head->datagram_payload.size); - ASSERT_EQ(HeaderSize + InterstellarWar.size(), chain.head->datagram_payload.size); - ASSERT_EQ(0, std::memcmp(makeHeader(meta, 0, false).data(), chain.head->datagram_payload.data, HeaderSize)); - ASSERT_EQ(0, - std::memcmp(InterstellarWar.data(), - static_cast(chain.head->datagram_payload.data) + HeaderSize, - InterstellarWar.size())); - ASSERT_EQ(&user_transfer_referent, chain.head->user_transfer_reference); - - // SECOND FRAME -- contains the last byte of the CRC. - std::cout << hexdump::hexdump(chain.tail->datagram_payload.data, chain.tail->datagram_payload.size) << "\n\n"; - ASSERT_EQ(223574680, chain.tail->deadline_usec); - ASSERT_EQ(55, chain.tail->dscp); - ASSERT_EQ(0xBABA'DEDAU, chain.tail->destination.ip_address); - ASSERT_EQ(0xD0ED, chain.tail->destination.udp_port); - ASSERT_EQ(HeaderSize + 4U, chain.tail->datagram_payload.size); - ASSERT_EQ(0, std::memcmp(makeHeader(meta, 1, true).data(), chain.tail->datagram_payload.data, HeaderSize)); - ASSERT_EQ(0, - std::memcmp(InterstellarWarCRC.data(), - static_cast(chain.tail->datagram_payload.data) + HeaderSize, - 4U)); - ASSERT_EQ(&user_transfer_referent, chain.tail->user_transfer_reference); -} - -TEST(TxPrivate, PushPeekPopFree) -{ - helpers::TestAllocator allocator; - const UdpardNodeID node_id = 1234; - // - UdpardTx tx{ - .local_node_id = &node_id, - .queue_capacity = 3, - .mtu = (EtherealStrength.size() + 4U + 3U) / 3U, - .dscp_value_per_priority = {0, 1, 2, 3, 4, 5, 6, 7}, - .memory = &allocator, - .queue_size = 0, - .root = nullptr, - }; - const TransferMetadata meta{ - .priority = UdpardPriorityNominal, - .src_node_id = 4321, - .dst_node_id = 5432, - .data_specifier = 7766, - .transfer_id = 0x0123'4567'89AB'CDEFULL, - }; - std::monostate user_transfer_referent; - ASSERT_EQ(3, - txPush(&tx, - 1234567890U, - meta, - {.ip_address = 0xBABA'DEDAU, .udp_port = 0xD0ED}, - {.size = EtherealStrength.size(), .data = EtherealStrength.data()}, - &user_transfer_referent)); - ASSERT_EQ(3, allocator.getNumAllocatedFragments()); - ASSERT_EQ(3 * (sizeof(TxItem) + HeaderSize) + EtherealStrength.size() + 4U, allocator.getTotalAllocatedAmount()); - ASSERT_EQ(3, tx.queue_size); - - const auto* frame = udpardTxPeek(&tx); - std::cout << hexdump::hexdump(frame->datagram_payload.data, frame->datagram_payload.size) << "\n\n"; - ASSERT_NE(nullptr, frame); - ASSERT_NE(nullptr, frame->next_in_transfer); - ASSERT_EQ(1234567890U, frame->deadline_usec); - ASSERT_EQ(4, frame->dscp); - ASSERT_EQ(0xBABA'DEDAU, frame->destination.ip_address); - ASSERT_EQ(0xD0ED, frame->destination.udp_port); - ASSERT_EQ(HeaderSize + tx.mtu, frame->datagram_payload.size); - ASSERT_EQ(0, std::memcmp(makeHeader(meta, 0, false).data(), frame->datagram_payload.data, HeaderSize)); - udpardTxFree(tx.memory, udpardTxPop(&tx, frame)); - - ASSERT_EQ(2, allocator.getNumAllocatedFragments()); - ASSERT_EQ(2, tx.queue_size); - - frame = udpardTxPeek(&tx); - std::cout << hexdump::hexdump(frame->datagram_payload.data, frame->datagram_payload.size) << "\n\n"; - ASSERT_NE(nullptr, frame); - ASSERT_NE(nullptr, frame->next_in_transfer); - ASSERT_EQ(1234567890U, frame->deadline_usec); - ASSERT_EQ(4, frame->dscp); - ASSERT_EQ(0xBABA'DEDAU, frame->destination.ip_address); - ASSERT_EQ(0xD0ED, frame->destination.udp_port); - ASSERT_EQ(HeaderSize + tx.mtu, frame->datagram_payload.size); - ASSERT_EQ(0, std::memcmp(makeHeader(meta, 1, false).data(), frame->datagram_payload.data, HeaderSize)); - udpardTxFree(tx.memory, udpardTxPop(&tx, frame)); - - ASSERT_EQ(1, allocator.getNumAllocatedFragments()); - ASSERT_EQ(1, tx.queue_size); - - frame = udpardTxPeek(&tx); - std::cout << hexdump::hexdump(frame->datagram_payload.data, frame->datagram_payload.size) << "\n\n"; - ASSERT_NE(nullptr, frame); - ASSERT_EQ(nullptr, frame->next_in_transfer); - ASSERT_EQ(1234567890U, frame->deadline_usec); - ASSERT_EQ(4, frame->dscp); - ASSERT_EQ(0xBABA'DEDAU, frame->destination.ip_address); - ASSERT_EQ(0xD0ED, frame->destination.udp_port); - ASSERT_EQ(HeaderSize + EtherealStrength.size() - 2 * tx.mtu + 4U, frame->datagram_payload.size); - ASSERT_EQ(0, std::memcmp(makeHeader(meta, 2, true).data(), frame->datagram_payload.data, HeaderSize)); - udpardTxFree(tx.memory, udpardTxPop(&tx, frame)); - - ASSERT_EQ(0, allocator.getNumAllocatedFragments()); - ASSERT_EQ(0, tx.queue_size); - ASSERT_EQ(nullptr, udpardTxPeek(&tx)); -} - -TEST(TxPrivate, PushPrioritization) -{ - helpers::TestAllocator allocator; - const UdpardNodeID node_id = 1234; - // - UdpardTx tx{ - .local_node_id = &node_id, - .queue_capacity = 7, - .mtu = 140, // This is chosen to match the test data. - .dscp_value_per_priority = {0, 1, 2, 3, 4, 5, 6, 7}, - .memory = &allocator, - .queue_size = 0, - .root = nullptr, - }; - // A -- Push the first multi-frame transfer at nominal priority level. - const TransferMetadata meta_a{ - .priority = UdpardPriorityNominal, - .src_node_id = 100, - .dst_node_id = UDPARD_NODE_ID_UNSET, - .data_specifier = 200, - .transfer_id = 5'000, - }; - ASSERT_EQ(3, - txPush(&tx, - 0, - meta_a, - {.ip_address = 0xAAAA'AAAA, .udp_port = 0xAAAA}, - {.size = EtherealStrength.size(), .data = EtherealStrength.data()}, - nullptr)); - ASSERT_EQ(3, allocator.getNumAllocatedFragments()); - ASSERT_EQ(3, tx.queue_size); - const auto* frame = udpardTxPeek(&tx); - ASSERT_NE(nullptr, frame); - ASSERT_EQ(0xAAAA'AAAA, frame->destination.ip_address); - - // B -- Next, push a higher-priority transfer and ensure it takes precedence. - ASSERT_EQ(1, - txPush(&tx, - 0, - { - .priority = UdpardPriorityHigh, - .src_node_id = 100, - .dst_node_id = UDPARD_NODE_ID_UNSET, - .data_specifier = 200, - .transfer_id = 100'000, - }, - {.ip_address = 0xBBBB'BBBB, .udp_port = 0xBBBB}, - {.size = DetailOfTheCosmos.size(), .data = DetailOfTheCosmos.data()}, - nullptr)); - ASSERT_EQ(4, allocator.getNumAllocatedFragments()); - ASSERT_EQ(4, tx.queue_size); - frame = udpardTxPeek(&tx); - ASSERT_NE(nullptr, frame); - ASSERT_EQ(0xBBBB'BBBB, frame->destination.ip_address); - - // C -- Next, push a lower-priority transfer and ensure it goes towards the back. - ASSERT_EQ(1, - txPush(&tx, - 1002, - { - .priority = UdpardPriorityLow, - .src_node_id = 100, - .dst_node_id = UDPARD_NODE_ID_UNSET, - .data_specifier = 200, - .transfer_id = 10'000, - }, - {.ip_address = 0xCCCC'CCCC, .udp_port = 0xCCCC}, - {.size = InterstellarWar.size(), .data = InterstellarWar.data()}, - nullptr)); - ASSERT_EQ(5, allocator.getNumAllocatedFragments()); - ASSERT_EQ(5, tx.queue_size); - frame = udpardTxPeek(&tx); - ASSERT_NE(nullptr, frame); - ASSERT_EQ(0xBBBB'BBBB, frame->destination.ip_address); - - // D -- Add another transfer like the previous one and ensure it goes in the back. - ASSERT_EQ(1, - txPush(&tx, - 1003, - { - .priority = UdpardPriorityLow, - .src_node_id = 100, - .dst_node_id = UDPARD_NODE_ID_UNSET, - .data_specifier = 200, - .transfer_id = 10'001, - }, - {.ip_address = 0xDDDD'DDDD, .udp_port = 0xDDDD}, - {.size = InterstellarWar.size(), .data = InterstellarWar.data()}, - nullptr)); - ASSERT_EQ(6, allocator.getNumAllocatedFragments()); - ASSERT_EQ(6, tx.queue_size); - frame = udpardTxPeek(&tx); - ASSERT_NE(nullptr, frame); - ASSERT_EQ(0xBBBB'BBBB, frame->destination.ip_address); - - // E -- Add an even higher priority transfer. - ASSERT_EQ(1, - txPush(&tx, - 1003, - { - .priority = UdpardPriorityFast, - .src_node_id = 100, - .dst_node_id = UDPARD_NODE_ID_UNSET, - .data_specifier = 200, - .transfer_id = 1'000, - }, - {.ip_address = 0xEEEE'EEEE, .udp_port = 0xEEEE}, - {.size = InterstellarWar.size(), .data = InterstellarWar.data()}, - nullptr)); - ASSERT_EQ(7, allocator.getNumAllocatedFragments()); - ASSERT_EQ(7, tx.queue_size); - frame = udpardTxPeek(&tx); - ASSERT_NE(nullptr, frame); - ASSERT_EQ(0xEEEE'EEEE, frame->destination.ip_address); - - // Now, unwind the queue and ensure the frames are popped in the right order. - // E - udpardTxFree(tx.memory, udpardTxPop(&tx, frame)); - ASSERT_EQ(6, allocator.getNumAllocatedFragments()); - ASSERT_EQ(6, tx.queue_size); - // B - frame = udpardTxPeek(&tx); - ASSERT_NE(nullptr, frame); - ASSERT_EQ(0xBBBB'BBBB, frame->destination.ip_address); - udpardTxFree(tx.memory, udpardTxPop(&tx, frame)); - ASSERT_EQ(5, allocator.getNumAllocatedFragments()); - ASSERT_EQ(5, tx.queue_size); - // A1, three frames. - frame = udpardTxPeek(&tx); - ASSERT_NE(nullptr, frame); - ASSERT_EQ(0xAAAA'AAAA, frame->destination.ip_address); - ASSERT_EQ(0, std::memcmp(makeHeader(meta_a, 0, false).data(), frame->datagram_payload.data, HeaderSize)); - udpardTxFree(tx.memory, udpardTxPop(&tx, frame)); - ASSERT_EQ(4, allocator.getNumAllocatedFragments()); - ASSERT_EQ(4, tx.queue_size); - // A2 - frame = udpardTxPeek(&tx); - ASSERT_NE(nullptr, frame); - ASSERT_EQ(0xAAAA'AAAA, frame->destination.ip_address); - ASSERT_EQ(0, std::memcmp(makeHeader(meta_a, 1, false).data(), frame->datagram_payload.data, HeaderSize)); - udpardTxFree(tx.memory, udpardTxPop(&tx, frame)); - ASSERT_EQ(3, allocator.getNumAllocatedFragments()); - ASSERT_EQ(3, tx.queue_size); - // A3 - frame = udpardTxPeek(&tx); - ASSERT_NE(nullptr, frame); - ASSERT_EQ(0xAAAA'AAAA, frame->destination.ip_address); - ASSERT_EQ(0, std::memcmp(makeHeader(meta_a, 2, true).data(), frame->datagram_payload.data, HeaderSize)); - udpardTxFree(tx.memory, udpardTxPop(&tx, frame)); - ASSERT_EQ(2, allocator.getNumAllocatedFragments()); - ASSERT_EQ(2, tx.queue_size); - // C - frame = udpardTxPeek(&tx); - ASSERT_NE(nullptr, frame); - ASSERT_EQ(0xCCCC'CCCC, frame->destination.ip_address); - udpardTxFree(tx.memory, udpardTxPop(&tx, frame)); - ASSERT_EQ(1, allocator.getNumAllocatedFragments()); - ASSERT_EQ(1, tx.queue_size); - // D - frame = udpardTxPeek(&tx); - ASSERT_NE(nullptr, frame); - ASSERT_EQ(0xDDDD'DDDD, frame->destination.ip_address); - udpardTxFree(tx.memory, udpardTxPop(&tx, frame)); - ASSERT_EQ(0, allocator.getNumAllocatedFragments()); - ASSERT_EQ(0, tx.queue_size); - - ASSERT_EQ(nullptr, udpardTxPeek(&tx)); -} - -TEST(TxPrivate, PushCapacityLimit) -{ - helpers::TestAllocator allocator; - const UdpardNodeID node_id = 1234; - // - UdpardTx tx{ - .local_node_id = &node_id, - .queue_capacity = 2, - .mtu = 10U, - .dscp_value_per_priority = {0, 1, 2, 3, 4, 5, 6, 7}, - .memory = &allocator, - .queue_size = 0, - .root = nullptr, - }; - const TransferMetadata meta{ - .priority = UdpardPriorityNominal, - .src_node_id = 4321, - .dst_node_id = 5432, - .data_specifier = 7766, - .transfer_id = 0x0123'4567'89AB'CDEFULL, - }; - ASSERT_EQ(-UDPARD_ERROR_CAPACITY, - txPush(&tx, - 1234567890U, - meta, - {.ip_address = 0xBABA'DEDAU, .udp_port = 0xD0ED}, - {.size = EtherealStrength.size(), .data = EtherealStrength.data()}, - nullptr)); - ASSERT_EQ(0, allocator.getNumAllocatedFragments()); - ASSERT_EQ(0, allocator.getTotalAllocatedAmount()); - ASSERT_EQ(0, tx.queue_size); -} - -TEST(TxPrivate, PushOOM) -{ - helpers::TestAllocator allocator; - const UdpardNodeID node_id = 1234; - // - UdpardTx tx{ - .local_node_id = &node_id, - .queue_capacity = 10'000U, - .mtu = (EtherealStrength.size() + 4U + 3U) / 3U, - .dscp_value_per_priority = {0, 1, 2, 3, 4, 5, 6, 7}, - .memory = &allocator, - .queue_size = 0, - .root = nullptr, - }; - const TransferMetadata meta{ - .priority = UdpardPriorityNominal, - .src_node_id = 4321, - .dst_node_id = 5432, - .data_specifier = 7766, - .transfer_id = 0x0123'4567'89AB'CDEFULL, - }; - allocator.setAllocationCeiling(EtherealStrength.size()); // No memory for the overheads. - ASSERT_EQ(-UDPARD_ERROR_MEMORY, - txPush(&tx, - 1234567890U, - meta, - {.ip_address = 0xBABA'DEDAU, .udp_port = 0xD0ED}, - {.size = EtherealStrength.size(), .data = EtherealStrength.data()}, - nullptr)); - ASSERT_EQ(0, allocator.getNumAllocatedFragments()); - ASSERT_EQ(0, allocator.getTotalAllocatedAmount()); - ASSERT_EQ(0, tx.queue_size); -} - -TEST(TxPrivate, PushAnonymousMultiFrame) -{ - helpers::TestAllocator allocator; - const UdpardNodeID node_id = 0xFFFFU; - // - UdpardTx tx{ - .local_node_id = &node_id, - .queue_capacity = 10'000U, - .mtu = (EtherealStrength.size() + 4U + 3U) / 3U, - .dscp_value_per_priority = {0, 1, 2, 3, 4, 5, 6, 7}, - .memory = &allocator, - .queue_size = 0, - .root = nullptr, - }; - const TransferMetadata meta{ - .priority = UdpardPriorityNominal, - .src_node_id = 4321, - .dst_node_id = 5432, - .data_specifier = 7766, - .transfer_id = 0x0123'4567'89AB'CDEFULL, - }; - ASSERT_EQ(-UDPARD_ERROR_ANONYMOUS, - txPush(&tx, - 1234567890U, - meta, - {.ip_address = 0xBABA'DEDAU, .udp_port = 0xD0ED}, - {.size = EtherealStrength.size(), .data = EtherealStrength.data()}, - nullptr)); - ASSERT_EQ(0, allocator.getNumAllocatedFragments()); - ASSERT_EQ(0, allocator.getTotalAllocatedAmount()); - ASSERT_EQ(0, tx.queue_size); -} - -TEST(TxPrivate, PushAnonymousService) -{ - helpers::TestAllocator allocator; - const UdpardNodeID node_id = 0xFFFFU; - // - UdpardTx tx{ - .local_node_id = &node_id, - .queue_capacity = 10'000, - .mtu = 1500, - .dscp_value_per_priority = {0, 1, 2, 3, 4, 5, 6, 7}, - .memory = &allocator, - .queue_size = 0, - .root = nullptr, - }; - const TransferMetadata meta{ - .priority = UdpardPriorityNominal, - .src_node_id = 4321, - .dst_node_id = 5432, - .data_specifier = 0x8099U, // Service response. - .transfer_id = 0x0123'4567'89AB'CDEFULL, - }; - ASSERT_EQ(-UDPARD_ERROR_ANONYMOUS, - txPush(&tx, - 1234567890U, - meta, - {.ip_address = 0xBABA'DEDAU, .udp_port = 0xD0ED}, - {.size = EtherealStrength.size(), .data = EtherealStrength.data()}, - nullptr)); - ASSERT_EQ(0, allocator.getNumAllocatedFragments()); - ASSERT_EQ(0, allocator.getTotalAllocatedAmount()); - ASSERT_EQ(0, tx.queue_size); -} diff --git a/tests/test_public_tx.cpp b/tests/test_public_tx.cpp deleted file mode 100644 index e6313ad..0000000 --- a/tests/test_public_tx.cpp +++ /dev/null @@ -1,535 +0,0 @@ -/// This software is distributed under the terms of the MIT License. -/// Copyright (C) OpenCyphal Development Team -/// Copyright Amazon.com Inc. or its affiliates. -/// SPDX-License-Identifier: MIT - -#include -#include "helpers.hpp" -#include "hexdump.hpp" -#include - -namespace -{ -constexpr std::string_view FleetingEvents = - "What was the human world like in the eyes of the mountains? Perhaps just something they saw on a leisurely " - "afternoon. First, a few small living beings appeared on the plain. After a while, they multiplied, and after " - "another while they erected structures like anthills that quickly filled the region. The structures shone from the " - "inside, and some of them let off smoke. After another while, the lights and smoke disappeared, and the small " - "things vanished as well, and then their structures toppled and were buried in the sand. That was all. Among the " - "countless things the mountains had witnessed, these fleeting events were not necessarily the most interesting."; -constexpr std::array FleetingEventsCRC{{26, 198, 18, 137}}; -} // namespace - -TEST(TxPublic, TxInit) -{ - std::monostate user_referent; - const UdpardNodeID node_id = 0; - { - UdpardMemoryResource memory{ - .allocate = &helpers::dummy_allocator::allocate, - .free = &helpers::dummy_allocator::free, - .user_reference = &user_referent, - }; - ASSERT_EQ(-UDPARD_ERROR_ARGUMENT, udpardTxInit(nullptr, &node_id, 0, &memory)); - } - { - UdpardTx tx{}; - UdpardMemoryResource memory{ - .allocate = &helpers::dummy_allocator::allocate, - .free = &helpers::dummy_allocator::free, - .user_reference = &user_referent, - }; - ASSERT_EQ(-UDPARD_ERROR_ARGUMENT, udpardTxInit(&tx, nullptr, 0, &memory)); - } - { - UdpardTx tx{}; - UdpardMemoryResource memory{ - .allocate = nullptr, - .free = &helpers::dummy_allocator::free, - .user_reference = &user_referent, - }; - ASSERT_EQ(-UDPARD_ERROR_ARGUMENT, udpardTxInit(&tx, &node_id, 0, &memory)); - } - { - UdpardTx tx{}; - UdpardMemoryResource memory{ - .allocate = &helpers::dummy_allocator::allocate, - .free = nullptr, - .user_reference = &user_referent, - }; - ASSERT_EQ(-UDPARD_ERROR_ARGUMENT, udpardTxInit(&tx, &node_id, 0, &memory)); - } - { - UdpardTx tx{}; - UdpardMemoryResource memory{ - .allocate = &helpers::dummy_allocator::allocate, - .free = &helpers::dummy_allocator::free, - .user_reference = &user_referent, - }; - ASSERT_EQ(0, udpardTxInit(&tx, &node_id, 0, &memory)); - ASSERT_EQ(&user_referent, tx.memory->user_reference); - ASSERT_EQ(UDPARD_MTU_DEFAULT, tx.mtu); - } -} - -TEST(TxPublic, Publish) -{ - helpers::TestAllocator allocator; - const UdpardNodeID node_id = 1234; - // - UdpardTx tx{ - .local_node_id = &node_id, - .queue_capacity = 1U, - .mtu = UDPARD_MTU_DEFAULT, - .dscp_value_per_priority = {0, 1, 2, 3, 4, 5, 6, 7}, - .memory = &allocator, - .queue_size = 0, - .root = nullptr, - }; - std::monostate user_transfer_referent; - UdpardTransferID transfer_id = 0; - ASSERT_EQ(1, - udpardTxPublish(&tx, - 1234567890, - UdpardPriorityNominal, - 0x1432, - &transfer_id, - {.size = FleetingEvents.size(), .data = FleetingEvents.data()}, - &user_transfer_referent)); - ASSERT_EQ(1, transfer_id); - ASSERT_EQ(1, allocator.getNumAllocatedFragments()); - ASSERT_EQ(1, tx.queue_size); - const auto* frame = udpardTxPeek(&tx); - std::cout << hexdump::hexdump(frame->datagram_payload.data, frame->datagram_payload.size) << "\n\n"; - ASSERT_NE(nullptr, frame); - ASSERT_EQ(nullptr, frame->next_in_transfer); - ASSERT_EQ(1234567890, frame->deadline_usec); - ASSERT_EQ(4, frame->dscp); - ASSERT_EQ(0xEF00'1432UL, frame->destination.ip_address); - ASSERT_EQ(9382, frame->destination.udp_port); - ASSERT_EQ(&user_transfer_referent, frame->user_transfer_reference); - ASSERT_EQ(24 + FleetingEvents.size() + 4, frame->datagram_payload.size); - ASSERT_EQ(0, - memcmp(static_cast(frame->datagram_payload.data) + 24, - FleetingEvents.data(), - FleetingEvents.size())); - ASSERT_EQ(0, - memcmp(static_cast(frame->datagram_payload.data) + 24 + FleetingEvents.size(), - FleetingEventsCRC.data(), - FleetingEventsCRC.size())); - udpardTxFree(tx.memory, udpardTxPop(&tx, frame)); - udpardTxFree(tx.memory, udpardTxPop(&tx, nullptr)); // No-op. - - // Out of queue; transfer-ID not incremented. - ASSERT_EQ(-UDPARD_ERROR_CAPACITY, - udpardTxPublish(&tx, - 1234567890, - UdpardPriorityNominal, - 0x1432, - &transfer_id, - {.size = tx.mtu * 2, .data = FleetingEvents.data()}, - nullptr)); - ASSERT_EQ(1, transfer_id); - - // Attempt to publish a multi-frame transfer with an anonymous local node. - { - auto tx_bad = tx; - const UdpardNodeID anonymous_node_id = 0xFFFFU; - tx_bad.queue_size = 1000; - tx_bad.mtu = 10; // Force multi-frame. - tx_bad.local_node_id = &anonymous_node_id; - ASSERT_EQ(-UDPARD_ERROR_ANONYMOUS, - udpardTxPublish(&tx_bad, - 1234567890, - UdpardPriorityNominal, - 0x1432, - &transfer_id, - {.size = FleetingEvents.size(), .data = FleetingEvents.data()}, - nullptr)); - ASSERT_EQ(1, transfer_id); - } - - // Invalid Tx. - ASSERT_EQ(-UDPARD_ERROR_ARGUMENT, - udpardTxPublish(nullptr, - 1234567890, - UdpardPriorityNominal, - 0x1432, - &transfer_id, - {.size = FleetingEvents.size(), .data = FleetingEvents.data()}, - nullptr)); - ASSERT_EQ(1, transfer_id); - // Invalid local node-ID. - { - auto tx_bad = tx; - tx_bad.local_node_id = nullptr; - ASSERT_EQ(-UDPARD_ERROR_ARGUMENT, - udpardTxPublish(&tx_bad, - 1234567890, - UdpardPriorityNominal, - 0x1432, - &transfer_id, - {.size = FleetingEvents.size(), .data = FleetingEvents.data()}, - nullptr)); - ASSERT_EQ(1, transfer_id); - } - // Invalid priority. - { - auto bad_priority = UdpardPriorityOptional; - ASSERT_EQ(-UDPARD_ERROR_ARGUMENT, - udpardTxPublish(&tx, - 1234567890, - (UdpardPriority) (bad_priority + 1), - 0x1432, - &transfer_id, - {.size = FleetingEvents.size(), .data = FleetingEvents.data()}, - nullptr)); - ASSERT_EQ(1, transfer_id); - } - // Invalid subject. - ASSERT_EQ(-UDPARD_ERROR_ARGUMENT, - udpardTxPublish(&tx, - 1234567890, - UdpardPriorityNominal, - 0xFFFFU, - &transfer_id, - {.size = FleetingEvents.size(), .data = FleetingEvents.data()}, - nullptr)); - ASSERT_EQ(1, transfer_id); - // Invalid transfer-ID pointer. - ASSERT_EQ(-UDPARD_ERROR_ARGUMENT, - udpardTxPublish(&tx, - 1234567890, - UdpardPriorityNominal, - 0x1432, - nullptr, - {.size = FleetingEvents.size(), .data = FleetingEvents.data()}, - nullptr)); - // Invalid payload pointer. - ASSERT_EQ(-UDPARD_ERROR_ARGUMENT, - udpardTxPublish(&tx, - 1234567890, - UdpardPriorityNominal, - 0x1432, - &transfer_id, - {.size = FleetingEvents.size(), .data = nullptr}, - nullptr)); - ASSERT_EQ(1, transfer_id); -} - -TEST(TxPublic, Request) -{ - helpers::TestAllocator allocator; - const UdpardNodeID node_id = 1234; - // - UdpardTx tx{ - .local_node_id = &node_id, - .queue_capacity = 1U, - .mtu = UDPARD_MTU_DEFAULT, - .dscp_value_per_priority = {0, 1, 2, 3, 4, 5, 6, 7}, - .memory = &allocator, - .queue_size = 0, - .root = nullptr, - }; - std::monostate user_transfer_referent; - UdpardTransferID transfer_id = 0; - ASSERT_EQ(1, - udpardTxRequest(&tx, - 1234567890, - UdpardPriorityNominal, - 0x123, - 0x1538, - &transfer_id, - {.size = FleetingEvents.size(), .data = FleetingEvents.data()}, - &user_transfer_referent)); - ASSERT_EQ(1, transfer_id); - ASSERT_EQ(1, allocator.getNumAllocatedFragments()); - ASSERT_EQ(1, tx.queue_size); - const auto* frame = udpardTxPeek(&tx); - std::cout << hexdump::hexdump(frame->datagram_payload.data, frame->datagram_payload.size) << "\n\n"; - ASSERT_NE(nullptr, frame); - ASSERT_EQ(nullptr, frame->next_in_transfer); - ASSERT_EQ(1234567890, frame->deadline_usec); - ASSERT_EQ(4, frame->dscp); - ASSERT_EQ(0xEF01'1538UL, frame->destination.ip_address); - ASSERT_EQ(9382, frame->destination.udp_port); - ASSERT_EQ(&user_transfer_referent, frame->user_transfer_reference); - ASSERT_EQ(24 + FleetingEvents.size() + 4, frame->datagram_payload.size); - ASSERT_EQ(0, - memcmp(static_cast(frame->datagram_payload.data) + 24, - FleetingEvents.data(), - FleetingEvents.size())); - ASSERT_EQ(0, - memcmp(static_cast(frame->datagram_payload.data) + 24 + FleetingEvents.size(), - FleetingEventsCRC.data(), - FleetingEventsCRC.size())); - udpardTxFree(tx.memory, udpardTxPop(&tx, frame)); - udpardTxFree(tx.memory, udpardTxPop(&tx, nullptr)); // No-op. - - // Out of queue; transfer-ID not incremented. - ASSERT_EQ(-UDPARD_ERROR_CAPACITY, - udpardTxRequest(&tx, - 1234567890, - UdpardPriorityNominal, - 0x123, - 0x1538, - &transfer_id, - {.size = tx.mtu * 2, .data = FleetingEvents.data()}, - nullptr)); - ASSERT_EQ(1, transfer_id); - - // Attempt to send a service transfer from an anonymous node. - { - auto tx_bad = tx; - const UdpardNodeID anonymous_node_id = 0xFFFFU; - tx_bad.queue_size = 1000; - tx_bad.mtu = 10; // Force multi-frame. - tx_bad.local_node_id = &anonymous_node_id; - ASSERT_EQ(-UDPARD_ERROR_ANONYMOUS, - udpardTxRequest(&tx_bad, - 1234567890, - UdpardPriorityNominal, - 0x123, - 0x1538, - &transfer_id, - {.size = FleetingEvents.size(), .data = FleetingEvents.data()}, - nullptr)); - ASSERT_EQ(1, transfer_id); - } - - // Invalid Tx. - ASSERT_EQ(-UDPARD_ERROR_ARGUMENT, - udpardTxRequest(nullptr, - 1234567890, - UdpardPriorityNominal, - 0x123, - 0x1538, - &transfer_id, - {.size = FleetingEvents.size(), .data = FleetingEvents.data()}, - nullptr)); - ASSERT_EQ(1, transfer_id); - // Invalid local node-ID. - { - auto tx_bad = tx; - tx_bad.local_node_id = nullptr; - ASSERT_EQ(-UDPARD_ERROR_ARGUMENT, - udpardTxRequest(&tx_bad, - 1234567890, - UdpardPriorityNominal, - 0x123, - 0x1538, - &transfer_id, - {.size = FleetingEvents.size(), .data = FleetingEvents.data()}, - nullptr)); - ASSERT_EQ(1, transfer_id); - } - // Invalid priority. - { - auto bad_priority = UdpardPriorityOptional; - ASSERT_EQ(-UDPARD_ERROR_ARGUMENT, - udpardTxRequest(&tx, - 1234567890, - (UdpardPriority) (bad_priority + 1), - 0x123, - 0x1538, - &transfer_id, - {.size = FleetingEvents.size(), .data = FleetingEvents.data()}, - nullptr)); - ASSERT_EQ(1, transfer_id); - } - // Invalid remote node-ID. - ASSERT_EQ(-UDPARD_ERROR_ARGUMENT, - udpardTxRequest(&tx, - 1234567890, - UdpardPriorityNominal, - 0x123, - 0xFFFF, - &transfer_id, - {.size = FleetingEvents.size(), .data = FleetingEvents.data()}, - nullptr)); - ASSERT_EQ(1, transfer_id); - // Invalid service-ID. - ASSERT_EQ(-UDPARD_ERROR_ARGUMENT, - udpardTxRequest(&tx, - 1234567890, - UdpardPriorityNominal, - 0xFFFFU, - 0x1538, - &transfer_id, - {.size = FleetingEvents.size(), .data = FleetingEvents.data()}, - nullptr)); - ASSERT_EQ(1, transfer_id); - // Invalid transfer-ID pointer. - ASSERT_EQ(-UDPARD_ERROR_ARGUMENT, - udpardTxRequest(&tx, - 1234567890, - UdpardPriorityNominal, - 0x123, - 0x1538, - nullptr, - {.size = FleetingEvents.size(), .data = FleetingEvents.data()}, - nullptr)); - // Invalid payload pointer. - ASSERT_EQ(-UDPARD_ERROR_ARGUMENT, - udpardTxRequest(&tx, - 1234567890, - UdpardPriorityNominal, - 0x123, - 0x1538, - &transfer_id, - {.size = FleetingEvents.size(), .data = nullptr}, - nullptr)); - ASSERT_EQ(1, transfer_id); -} - -TEST(TxPublic, Response) -{ - helpers::TestAllocator allocator; - const UdpardNodeID node_id = 1234; - // - UdpardTx tx{ - .local_node_id = &node_id, - .queue_capacity = 1U, - .mtu = UDPARD_MTU_DEFAULT, - .dscp_value_per_priority = {0, 1, 2, 3, 4, 5, 6, 7}, - .memory = &allocator, - .queue_size = 0, - .root = nullptr, - }; - std::monostate user_transfer_referent; - ASSERT_EQ(1, - udpardTxRespond(&tx, - 1234567890, - UdpardPriorityNominal, - 0x123, - 0x1538, - 9876543210, - {.size = FleetingEvents.size(), .data = FleetingEvents.data()}, - &user_transfer_referent)); - ASSERT_EQ(1, allocator.getNumAllocatedFragments()); - ASSERT_EQ(1, tx.queue_size); - const auto* frame = udpardTxPeek(&tx); - std::cout << hexdump::hexdump(frame->datagram_payload.data, frame->datagram_payload.size) << "\n\n"; - ASSERT_NE(nullptr, frame); - ASSERT_EQ(nullptr, frame->next_in_transfer); - ASSERT_EQ(1234567890, frame->deadline_usec); - ASSERT_EQ(4, frame->dscp); - ASSERT_EQ(0xEF01'1538UL, frame->destination.ip_address); - ASSERT_EQ(9382, frame->destination.udp_port); - ASSERT_EQ(&user_transfer_referent, frame->user_transfer_reference); - ASSERT_EQ(24 + FleetingEvents.size() + 4, frame->datagram_payload.size); - ASSERT_EQ(0, - memcmp(static_cast(frame->datagram_payload.data) + 24, - FleetingEvents.data(), - FleetingEvents.size())); - ASSERT_EQ(0, - memcmp(static_cast(frame->datagram_payload.data) + 24 + FleetingEvents.size(), - FleetingEventsCRC.data(), - FleetingEventsCRC.size())); - udpardTxFree(tx.memory, udpardTxPop(&tx, frame)); - udpardTxFree(tx.memory, udpardTxPop(&tx, nullptr)); // No-op. - - // Out of queue; transfer-ID not incremented. - ASSERT_EQ(-UDPARD_ERROR_CAPACITY, - udpardTxRespond(&tx, - 1234567890, - UdpardPriorityNominal, - 0x123, - 0x1538, - 0, - {.size = tx.mtu * 2, .data = FleetingEvents.data()}, - nullptr)); - - // Attempt to send a service transfer from an anonymous node. - { - auto tx_bad = tx; - const UdpardNodeID anonymous_node_id = 0xFFFFU; - tx_bad.queue_size = 1000; - tx_bad.mtu = 10; // Force multi-frame. - tx_bad.local_node_id = &anonymous_node_id; - ASSERT_EQ(-UDPARD_ERROR_ANONYMOUS, - udpardTxRespond(&tx_bad, - 1234567890, - UdpardPriorityNominal, - 0x123, - 0x1538, - 0, - {.size = FleetingEvents.size(), .data = FleetingEvents.data()}, - nullptr)); - } - - // Invalid Tx. - ASSERT_EQ(-UDPARD_ERROR_ARGUMENT, - udpardTxRespond(nullptr, - 1234567890, - UdpardPriorityNominal, - 0x123, - 0x1538, - 0, - {.size = FleetingEvents.size(), .data = FleetingEvents.data()}, - nullptr)); - // Invalid local node-ID. - { - auto tx_bad = tx; - tx_bad.local_node_id = nullptr; - ASSERT_EQ(-UDPARD_ERROR_ARGUMENT, - udpardTxRespond(&tx_bad, - 1234567890, - UdpardPriorityNominal, - 0x123, - 0x1538, - 0, - {.size = FleetingEvents.size(), .data = FleetingEvents.data()}, - nullptr)); - } - // Invalid priority. - { - auto bad_priority = UdpardPriorityOptional; - ASSERT_EQ(-UDPARD_ERROR_ARGUMENT, - udpardTxRespond(&tx, - 1234567890, - (UdpardPriority) (bad_priority + 1), - 0x123, - 0x1538, - 0, - {.size = FleetingEvents.size(), .data = FleetingEvents.data()}, - nullptr)); - } - // Invalid remote node-ID. - ASSERT_EQ(-UDPARD_ERROR_ARGUMENT, - udpardTxRespond(&tx, - 1234567890, - UdpardPriorityNominal, - 0x123, - 0xFFFF, - 0, - {.size = FleetingEvents.size(), .data = FleetingEvents.data()}, - nullptr)); - // Invalid service-ID. - ASSERT_EQ(-UDPARD_ERROR_ARGUMENT, - udpardTxRespond(&tx, - 1234567890, - UdpardPriorityNominal, - 0xFFFFU, - 0x1538, - 0, - {.size = FleetingEvents.size(), .data = FleetingEvents.data()}, - nullptr)); - // Invalid payload pointer. - ASSERT_EQ(-UDPARD_ERROR_ARGUMENT, - udpardTxRespond(&tx, - 1234567890, - UdpardPriorityNominal, - 0x123, - 0x1538, - 0, - {.size = FleetingEvents.size(), .data = nullptr}, - nullptr)); -} - -TEST(TxPublic, PeekPopFreeNULL) // Just make sure we don't crash. -{ - ASSERT_EQ(nullptr, udpardTxPeek(nullptr)); - ASSERT_EQ(nullptr, udpardTxPop(nullptr, nullptr)); - udpardTxFree(nullptr, nullptr); -} diff --git a/tests/test_self.cpp b/tests/test_self.cpp deleted file mode 100644 index 929ff65..0000000 --- a/tests/test_self.cpp +++ /dev/null @@ -1,51 +0,0 @@ -// This software is distributed under the terms of the MIT License. -// Copyright (c) 2016-2020 OpenCyphal Development Team. - -#include "helpers.hpp" -#include - -TEST(TestAllocator, Basic) -{ - helpers::TestAllocator al; - - ASSERT_EQ(0, al.getNumAllocatedFragments()); - ASSERT_EQ(std::numeric_limits::max(), al.getAllocationCeiling()); - - auto* a = al.allocate(123); - ASSERT_EQ(1, al.getNumAllocatedFragments()); - ASSERT_EQ(123, al.getTotalAllocatedAmount()); - - auto* b = al.allocate(456); - ASSERT_EQ(2, al.getNumAllocatedFragments()); - ASSERT_EQ(579, al.getTotalAllocatedAmount()); - - al.setAllocationCeiling(600); - - ASSERT_EQ(nullptr, al.allocate(100)); - ASSERT_EQ(2, al.getNumAllocatedFragments()); - ASSERT_EQ(579, al.getTotalAllocatedAmount()); - - auto* c = al.allocate(21); - ASSERT_EQ(3, al.getNumAllocatedFragments()); - ASSERT_EQ(600, al.getTotalAllocatedAmount()); - - al.free(123, a); - ASSERT_EQ(2, al.getNumAllocatedFragments()); - ASSERT_EQ(477, al.getTotalAllocatedAmount()); - - auto* d = al.allocate(100); - ASSERT_EQ(3, al.getNumAllocatedFragments()); - ASSERT_EQ(577, al.getTotalAllocatedAmount()); - - al.free(21, c); - ASSERT_EQ(2, al.getNumAllocatedFragments()); - ASSERT_EQ(556, al.getTotalAllocatedAmount()); - - al.free(100, d); - ASSERT_EQ(1, al.getNumAllocatedFragments()); - ASSERT_EQ(456, al.getTotalAllocatedAmount()); - - al.free(456, b); - ASSERT_EQ(0, al.getNumAllocatedFragments()); - ASSERT_EQ(0, al.getTotalAllocatedAmount()); -} diff --git a/tests/udpard_config_private.h b/tests/udpard_config_private.h deleted file mode 100644 index 57172a7..0000000 --- a/tests/udpard_config_private.h +++ /dev/null @@ -1,4 +0,0 @@ -// This libudpard config header is included from udpard.c via UDPARD_CONFIG_HEADER - -// Expose the internal definitions for testing. -#define UDPARD_PRIVATE