diff --git a/CMakeLists.txt b/CMakeLists.txt index 862a521fb..bd3168f8c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,3 +1,13 @@ +# SPDX-FileCopyrightText: 2019-2026 Connor McLaughlin +# SPDX-License-Identifier: CC-BY-NC-ND-4.0 + Packaging Restriction +# +# NOTE: In addition to the terms of CC-BY-NC-ND-4.0, you may not use this file to create +# packages or build recipes without explicit permission from the copyright holder. +# +# Unless otherwise specified, other files supporting the build system are covered under +# the same terms. +# + cmake_minimum_required(VERSION 3.19) project(duckstation C CXX) diff --git a/CMakeModules/DuckStationBuildSummary.cmake b/CMakeModules/DuckStationBuildSummary.cmake index b45b86ec3..dd155ae85 100644 --- a/CMakeModules/DuckStationBuildSummary.cmake +++ b/CMakeModules/DuckStationBuildSummary.cmake @@ -26,13 +26,23 @@ if(BUILD_TESTS) message(STATUS "Building unit tests.") endif() -# Refuse to build in Arch package environments. My license does not allow for packages, and I'm sick of -# dealing with people complaining about things broken by packagers. This is why we can't have nice things. -if(DEFINED ENV{DEBUGINFOD_URLS}) - if($ENV{DEBUGINFOD_URLS} MATCHES ".*archlinux.*") +# Refuse to build in hostile package environments. The code and build script licenses do not allow for +# packages, and I'm sick of dealing with people complaining about things broken by packagers, and then +# being attacked by package maintainers who violate their distribution's codes of conduct. Attempts to +# request removal of these packages have been unsuccessful, so we have to resort to this. +# NOTE: You do NOT have permission to distribute build scripts or patches that modify the build system +# without explicit permission from the copyright holder. +# DuckStation's code is public so it can be audited and learned from. Not to repackage. +# This is why we can't have nice things. +if(EXISTS /etc/os-release) + file(READ /etc/os-release OS_RELEASE_CONTENT) + if(OS_RELEASE_CONTENT MATCHES "ID=arch" OR OS_RELEASE_CONTENT MATCHES "ID_LIKE=arch" OR OS_RELEASE_CONTENT MATCHES "ID=nixos") message(FATAL_ERROR "Unsupported environment.") endif() endif() +if(DEFINED ENV{NIX_BUILD_TOP} OR DEFINED ENV{NIX_STORE} OR DEFINED ENV{IN_NIX_SHELL} OR EXISTS "/etc/NIXOS") + message(FATAL_ERROR "Unsupported environment.") +endif() if(DEFINED HOST_MIN_PAGE_SIZE AND DEFINED HOST_MAX_PAGE_SIZE) message(STATUS "Building with a dynamic page size of ${HOST_MIN_PAGE_SIZE} - ${HOST_MAX_PAGE_SIZE} bytes.") diff --git a/src/common-tests/CMakeLists.txt b/src/common-tests/CMakeLists.txt index 35ef80dee..b44d8e192 100644 --- a/src/common-tests/CMakeLists.txt +++ b/src/common-tests/CMakeLists.txt @@ -1,11 +1,14 @@ add_executable(common-tests + binary_reader_writer_tests.cpp bitutils_tests.cpp file_system_tests.cpp gsvector_tests.cpp gsvector_yuvtorgb_test.cpp hash_tests.cpp + heap_array_tests.cpp path_tests.cpp rectangle_tests.cpp + small_string_tests.cpp string_tests.cpp ) diff --git a/src/common-tests/binary_reader_writer_tests.cpp b/src/common-tests/binary_reader_writer_tests.cpp new file mode 100644 index 000000000..25ebb2b70 --- /dev/null +++ b/src/common-tests/binary_reader_writer_tests.cpp @@ -0,0 +1,868 @@ +// SPDX-FileCopyrightText: 2019-2026 Connor McLaughlin +// SPDX-License-Identifier: CC-BY-NC-ND-4.0 + +#include "common/binary_reader_writer.h" +#include "common/small_string.h" + +#include +#include +#include +#include +#include +#include + +using namespace std::string_view_literals; + +////////////////////////////////////////////////////////////////////////// +// Global test data +////////////////////////////////////////////////////////////////////////// + +// Buffer with various primitive types +alignas(8) static constexpr std::array g_primitive_buffer = {{ + 0x01, // u8: 1 + 0xFE, // s8: -2 + 0x34, 0x12, // u16: 0x1234 (little endian) + 0xCD, 0xAB, // s16: -0x5433 (little endian) + 0x78, 0x56, 0x34, 0x12, // u32: 0x12345678 + 0x88, 0xA9, 0xCB, 0xED, // s32: -0x12345678 + 0xEF, 0xCD, 0xAB, 0x89, 0x67, 0x45, 0x23, 0x01, // u64: 0x0123456789ABCDEF + 0x00, 0x00, 0x80, 0x3F, // float: 1.0f + 0x00, 0x00, 0x00, 0x00 // padding +}}; + +// Buffer with C-strings (null-terminated) +static constexpr std::array g_cstring_buffer = {{ + 'H', 'e', 'l', 'l', 'o', '\0', // "Hello" + 'W', 'o', 'r', 'l', 'd', '\0', // "World" + '\0', // empty string + 'A', 'B', 'C' // "ABC" without null terminator (truncated) +}}; + +// Buffer with size-prefixed strings (u32 length + data) +static constexpr std::array g_size_prefixed_buffer = { + 0x05, 0x00, 0x00, 0x00, // length: 5 + 'H', 'e', 'l', 'l', 'o', // "Hello" + 0x00, 0x00, 0x00, 0x00, // length: 0 (empty string) + 0x04, 0x00, 0x00, 0x00, // length: 4 + 'T', 'e', 's', 't', // "Test" + 0xFF, 0xFF, 0xFF, 0xFF // invalid length (overflow) +}; + +////////////////////////////////////////////////////////////////////////// +// BinarySpanReader Tests +////////////////////////////////////////////////////////////////////////// + +TEST(BinarySpanReader, DefaultConstructor) +{ + BinarySpanReader reader; + EXPECT_FALSE(reader.IsValid()); + EXPECT_EQ(reader.GetBufferRemaining(), 0u); + EXPECT_EQ(reader.GetBufferConsumed(), 0u); +} + +TEST(BinarySpanReader, SpanConstructor) +{ + BinarySpanReader reader(g_primitive_buffer); + EXPECT_TRUE(reader.IsValid()); + EXPECT_EQ(reader.GetBufferRemaining(), g_primitive_buffer.size()); + EXPECT_EQ(reader.GetBufferConsumed(), 0u); +} + +TEST(BinarySpanReader, MoveConstructor) +{ + BinarySpanReader original(g_primitive_buffer); + original.ReadU8(); + original.ReadU8(); + + BinarySpanReader moved(std::move(original)); + EXPECT_TRUE(moved.IsValid()); + EXPECT_EQ(moved.GetBufferConsumed(), 2u); + EXPECT_EQ(original.GetBufferConsumed(), 0u); +} + +TEST(BinarySpanReader, MoveAssignment) +{ + BinarySpanReader original(g_primitive_buffer); + original.ReadU8(); + + BinarySpanReader moved; + moved = std::move(original); + EXPECT_TRUE(moved.IsValid()); + EXPECT_EQ(moved.GetBufferConsumed(), 1u); + EXPECT_EQ(original.GetBufferConsumed(), 0u); +} + +TEST(BinarySpanReader, GetSpan) +{ + BinarySpanReader reader(g_primitive_buffer); + auto span = reader.GetSpan(); + EXPECT_EQ(span.size(), g_primitive_buffer.size()); + EXPECT_EQ(span.data(), g_primitive_buffer.data()); +} + +TEST(BinarySpanReader, CheckRemaining) +{ + BinarySpanReader reader(g_primitive_buffer); + EXPECT_TRUE(reader.CheckRemaining(g_primitive_buffer.size())); + EXPECT_TRUE(reader.CheckRemaining(1)); + EXPECT_FALSE(reader.CheckRemaining(g_primitive_buffer.size() + 1)); +} + +TEST(BinarySpanReader, ReadU8) +{ + BinarySpanReader reader(g_primitive_buffer); + u8 val; + EXPECT_TRUE(reader.ReadU8(&val)); + EXPECT_EQ(val, 0x01u); + EXPECT_EQ(reader.GetBufferConsumed(), 1u); +} + +TEST(BinarySpanReader, ReadS8) +{ + BinarySpanReader reader(g_primitive_buffer); + reader.IncrementPosition(1); + s8 val; + EXPECT_TRUE(reader.ReadS8(&val)); + EXPECT_EQ(val, static_cast(0xFE)); + EXPECT_EQ(reader.GetBufferConsumed(), 2u); +} + +TEST(BinarySpanReader, ReadU16) +{ + BinarySpanReader reader(g_primitive_buffer); + reader.IncrementPosition(2); + u16 val; + EXPECT_TRUE(reader.ReadU16(&val)); + EXPECT_EQ(val, 0x1234u); +} + +TEST(BinarySpanReader, ReadS16) +{ + BinarySpanReader reader(g_primitive_buffer); + reader.IncrementPosition(4); + s16 val; + EXPECT_TRUE(reader.ReadS16(&val)); + EXPECT_EQ(val, static_cast(0xABCD)); +} + +TEST(BinarySpanReader, ReadU32) +{ + BinarySpanReader reader(g_primitive_buffer); + reader.IncrementPosition(6); + u32 val; + EXPECT_TRUE(reader.ReadU32(&val)); + EXPECT_EQ(val, 0x12345678u); +} + +TEST(BinarySpanReader, ReadS32) +{ + BinarySpanReader reader(g_primitive_buffer); + reader.IncrementPosition(10); + s32 val; + EXPECT_TRUE(reader.ReadS32(&val)); + EXPECT_EQ(val, static_cast(0xEDCBA988)); +} + +TEST(BinarySpanReader, ReadU64) +{ + BinarySpanReader reader(g_primitive_buffer); + reader.IncrementPosition(14); + u64 val; + EXPECT_TRUE(reader.ReadU64(&val)); + EXPECT_EQ(val, 0x0123456789ABCDEFull); +} + +TEST(BinarySpanReader, ReadFloat) +{ + BinarySpanReader reader(g_primitive_buffer); + reader.IncrementPosition(22); + float val; + EXPECT_TRUE(reader.ReadFloat(&val)); + EXPECT_FLOAT_EQ(val, 1.0f); +} + +TEST(BinarySpanReader, ReadBool) +{ + std::array buf = {0x00, 0x01}; + BinarySpanReader reader(buf); + bool val; + EXPECT_TRUE(reader.ReadBool(&val)); + EXPECT_FALSE(val); + EXPECT_TRUE(reader.ReadBool(&val)); + EXPECT_TRUE(val); +} + +TEST(BinarySpanReader, ReadTReturningValue) +{ + BinarySpanReader reader(g_primitive_buffer); + EXPECT_EQ(reader.ReadU8(), 0x01u); + EXPECT_EQ(reader.ReadS8(), static_cast(0xFE)); + EXPECT_EQ(reader.ReadU16(), 0x1234u); + EXPECT_EQ(reader.ReadS16(), static_cast(0xABCD)); + EXPECT_EQ(reader.ReadU32(), 0x12345678u); + EXPECT_EQ(reader.ReadS32(), static_cast(0xEDCBA988)); + EXPECT_EQ(reader.ReadU64(), 0x0123456789ABCDEFull); + EXPECT_FLOAT_EQ(reader.ReadFloat(), 1.0f); +} + +TEST(BinarySpanReader, ReadBoolReturningValue) +{ + std::array buf = {0x00, 0x01, 0xFF}; + BinarySpanReader reader(buf); + EXPECT_FALSE(reader.ReadBool()); + EXPECT_TRUE(reader.ReadBool()); + EXPECT_TRUE(reader.ReadBool()); // any non-zero is true +} + +TEST(BinarySpanReader, ReadCStringToString) +{ + BinarySpanReader reader(g_cstring_buffer); + std::string val; + EXPECT_TRUE(reader.ReadCString(&val)); + EXPECT_EQ(val, "Hello"); + EXPECT_EQ(reader.GetBufferConsumed(), 6u); +} + +TEST(BinarySpanReader, ReadCStringToStringView) +{ + BinarySpanReader reader(g_cstring_buffer); + std::string_view val; + EXPECT_TRUE(reader.ReadCString(&val)); + EXPECT_EQ(val, "Hello"sv); +} + +TEST(BinarySpanReader, ReadCStringToSmallString) +{ + BinarySpanReader reader(g_cstring_buffer); + SmallString val; + EXPECT_TRUE(reader.ReadCString(&val)); + EXPECT_STREQ(val.c_str(), "Hello"); +} + +TEST(BinarySpanReader, ReadCStringReturningValue) +{ + BinarySpanReader reader(g_cstring_buffer); + EXPECT_EQ(reader.ReadCString(), "Hello"sv); + EXPECT_EQ(reader.ReadCString(), "World"sv); + EXPECT_EQ(reader.ReadCString(), ""sv); +} + +TEST(BinarySpanReader, ReadCStringWithoutNullTerminator) +{ + // Buffer ending without null terminator + std::array buf = {'A', 'B', 'C'}; + BinarySpanReader reader(buf); + std::string val; + EXPECT_FALSE(reader.ReadCString(&val)); +} + +TEST(BinarySpanReader, ReadSizePrefixedStringToString) +{ + BinarySpanReader reader(g_size_prefixed_buffer); + std::string val; + EXPECT_TRUE(reader.ReadSizePrefixedString(&val)); + EXPECT_EQ(val, "Hello"); + EXPECT_EQ(reader.GetBufferConsumed(), 9u); +} + +TEST(BinarySpanReader, ReadSizePrefixedStringToStringView) +{ + BinarySpanReader reader(g_size_prefixed_buffer); + std::string_view val; + EXPECT_TRUE(reader.ReadSizePrefixedString(&val)); + EXPECT_EQ(val, "Hello"sv); +} + +TEST(BinarySpanReader, ReadSizePrefixedStringToSmallString) +{ + BinarySpanReader reader(g_size_prefixed_buffer); + SmallString val; + EXPECT_TRUE(reader.ReadSizePrefixedString(&val)); + EXPECT_STREQ(val.c_str(), "Hello"); +} + +TEST(BinarySpanReader, ReadSizePrefixedStringReturningValue) +{ + BinarySpanReader reader(g_size_prefixed_buffer); + EXPECT_EQ(reader.ReadSizePrefixedString(), "Hello"sv); + EXPECT_EQ(reader.ReadSizePrefixedString(), ""sv); + EXPECT_EQ(reader.ReadSizePrefixedString(), "Test"sv); +} + +TEST(BinarySpanReader, ReadSizePrefixedStringEmpty) +{ + BinarySpanReader reader(g_size_prefixed_buffer); + reader.IncrementPosition(9); + std::string val; + EXPECT_TRUE(reader.ReadSizePrefixedString(&val)); + EXPECT_TRUE(val.empty()); +} + +TEST(BinarySpanReader, PeekU8) +{ + BinarySpanReader reader(g_primitive_buffer); + u8 val; + EXPECT_TRUE(reader.PeekU8(&val)); + EXPECT_EQ(val, 0x01u); + EXPECT_EQ(reader.GetBufferConsumed(), 0u); // position unchanged +} + +TEST(BinarySpanReader, PeekU16) +{ + BinarySpanReader reader(g_primitive_buffer); + reader.IncrementPosition(2); + u16 val; + EXPECT_TRUE(reader.PeekU16(&val)); + EXPECT_EQ(val, 0x1234u); + EXPECT_EQ(reader.GetBufferConsumed(), 2u); // position unchanged after peek +} + +TEST(BinarySpanReader, PeekU32) +{ + BinarySpanReader reader(g_primitive_buffer); + reader.IncrementPosition(6); + u32 val; + EXPECT_TRUE(reader.PeekU32(&val)); + EXPECT_EQ(val, 0x12345678u); + EXPECT_EQ(reader.GetBufferConsumed(), 6u); +} + +TEST(BinarySpanReader, PeekU64) +{ + BinarySpanReader reader(g_primitive_buffer); + reader.IncrementPosition(14); + u64 val; + EXPECT_TRUE(reader.PeekU64(&val)); + EXPECT_EQ(val, 0x0123456789ABCDEFull); + EXPECT_EQ(reader.GetBufferConsumed(), 14u); +} + +TEST(BinarySpanReader, PeekFloat) +{ + BinarySpanReader reader(g_primitive_buffer); + reader.IncrementPosition(22); + float val; + EXPECT_TRUE(reader.PeekFloat(&val)); + EXPECT_FLOAT_EQ(val, 1.0f); + EXPECT_EQ(reader.GetBufferConsumed(), 22u); +} + +TEST(BinarySpanReader, PeekBool) +{ + std::array buf = {0x00, 0x01}; + BinarySpanReader reader(buf); + bool val; + EXPECT_TRUE(reader.PeekBool(&val)); + EXPECT_FALSE(val); + EXPECT_EQ(reader.GetBufferConsumed(), 0u); +} + +TEST(BinarySpanReader, PeekCString) +{ + BinarySpanReader reader(g_cstring_buffer); + std::string_view val; + EXPECT_TRUE(reader.PeekCString(&val)); + EXPECT_EQ(val, "Hello"sv); + EXPECT_EQ(reader.GetBufferConsumed(), 0u); // position unchanged +} + +TEST(BinarySpanReader, PeekSizePrefixedString) +{ + BinarySpanReader reader(g_size_prefixed_buffer); + std::string_view val; + EXPECT_TRUE(reader.PeekSizePrefixedString(&val)); + EXPECT_EQ(val, "Hello"sv); + EXPECT_EQ(reader.GetBufferConsumed(), 0u); // position unchanged +} + +TEST(BinarySpanReader, StreamOperators) +{ + BinarySpanReader reader(g_primitive_buffer); + u8 u8val; + s8 s8val; + u16 u16val; + s16 s16val; + u32 u32val; + s32 s32val; + u64 u64val; + float fval; + + reader >> u8val >> s8val >> u16val >> s16val >> u32val >> s32val >> u64val >> fval; + + EXPECT_EQ(u8val, 0x01u); + EXPECT_EQ(s8val, static_cast(0xFE)); + EXPECT_EQ(u16val, 0x1234u); + EXPECT_EQ(s16val, static_cast(0xABCD)); + EXPECT_EQ(u32val, 0x12345678u); + EXPECT_EQ(s32val, static_cast(0xEDCBA988)); + EXPECT_EQ(u64val, 0x0123456789ABCDEFull); + EXPECT_FLOAT_EQ(fval, 1.0f); +} + +TEST(BinarySpanReader, StreamOperatorCString) +{ + BinarySpanReader reader(g_cstring_buffer); + std::string_view val; + reader >> val; + EXPECT_EQ(val, "Hello"sv); +} + +TEST(BinarySpanReader, ReadOptionalTWithValue) +{ + std::array buf = {0x01, 0x78, 0x56, 0x34, 0x12}; // has_value=true, value=0x12345678 + BinarySpanReader reader(buf); + std::optional val; + EXPECT_TRUE(reader.ReadOptionalT(&val)); + EXPECT_TRUE(val.has_value()); + EXPECT_EQ(val.value(), 0x12345678u); +} + +TEST(BinarySpanReader, ReadOptionalTWithoutValue) +{ + std::array buf = {0x00, 0x00, 0x00, 0x00, 0x00}; // has_value=false + BinarySpanReader reader(buf); + std::optional val = 123u; // preset to non-empty + EXPECT_TRUE(reader.ReadOptionalT(&val)); + EXPECT_FALSE(val.has_value()); +} + +TEST(BinarySpanReader, GetRemainingSpan) +{ + BinarySpanReader reader(g_primitive_buffer); + reader.IncrementPosition(10); + auto remaining = reader.GetRemainingSpan(); + EXPECT_EQ(remaining.size(), g_primitive_buffer.size() - 10); +} + +TEST(BinarySpanReader, GetRemainingSpanWithSize) +{ + BinarySpanReader reader(g_primitive_buffer); + reader.IncrementPosition(10); + auto remaining = reader.GetRemainingSpan(5); + EXPECT_EQ(remaining.size(), 5u); +} + +TEST(BinarySpanReader, ReadBeyondBuffer) +{ + std::array buf = {0x01, 0x02}; + BinarySpanReader reader(buf); + u32 val; + EXPECT_FALSE(reader.ReadU32(&val)); +} + +TEST(BinarySpanReader, PeekBeyondBuffer) +{ + std::array buf = {0x01, 0x02}; + BinarySpanReader reader(buf); + u32 val; + EXPECT_FALSE(reader.PeekU32(&val)); +} + +////////////////////////////////////////////////////////////////////////// +// BinarySpanWriter Tests +////////////////////////////////////////////////////////////////////////// + +TEST(BinarySpanWriter, DefaultConstructor) +{ + BinarySpanWriter writer; + EXPECT_FALSE(writer.IsValid()); + EXPECT_EQ(writer.GetBufferRemaining(), 0u); + EXPECT_EQ(writer.GetBufferWritten(), 0u); +} + +TEST(BinarySpanWriter, SpanConstructor) +{ + std::array buf = {}; + BinarySpanWriter writer(buf); + EXPECT_TRUE(writer.IsValid()); + EXPECT_EQ(writer.GetBufferRemaining(), buf.size()); + EXPECT_EQ(writer.GetBufferWritten(), 0u); +} + +TEST(BinarySpanWriter, MoveConstructor) +{ + std::array buf = {}; + BinarySpanWriter original(buf); + original.WriteU8(0x01); + original.WriteU8(0x02); + + BinarySpanWriter moved(std::move(original)); + EXPECT_TRUE(moved.IsValid()); + EXPECT_EQ(moved.GetBufferWritten(), 2u); + EXPECT_EQ(original.GetBufferWritten(), 0u); +} + +TEST(BinarySpanWriter, MoveAssignment) +{ + std::array buf = {}; + BinarySpanWriter original(buf); + original.WriteU8(0x01); + + BinarySpanWriter moved; + moved = std::move(original); + EXPECT_TRUE(moved.IsValid()); + EXPECT_EQ(moved.GetBufferWritten(), 1u); + EXPECT_EQ(original.GetBufferWritten(), 0u); +} + +TEST(BinarySpanWriter, GetSpan) +{ + std::array buf = {}; + BinarySpanWriter writer(buf); + auto span = writer.GetSpan(); + EXPECT_EQ(span.size(), buf.size()); + EXPECT_EQ(span.data(), buf.data()); +} + +TEST(BinarySpanWriter, WriteU8) +{ + std::array buf = {}; + BinarySpanWriter writer(buf); + EXPECT_TRUE(writer.WriteU8(0xAB)); + EXPECT_EQ(buf[0], 0xABu); + EXPECT_EQ(writer.GetBufferWritten(), 1u); +} + +TEST(BinarySpanWriter, WriteS8) +{ + std::array buf = {}; + BinarySpanWriter writer(buf); + EXPECT_TRUE(writer.WriteS8(-5)); + EXPECT_EQ(static_cast(buf[0]), -5); +} + +TEST(BinarySpanWriter, WriteU16) +{ + std::array buf = {}; + BinarySpanWriter writer(buf); + EXPECT_TRUE(writer.WriteU16(0x1234)); + EXPECT_EQ(buf[0], 0x34u); + EXPECT_EQ(buf[1], 0x12u); +} + +TEST(BinarySpanWriter, WriteS16) +{ + std::array buf = {}; + BinarySpanWriter writer(buf); + EXPECT_TRUE(writer.WriteS16(-1000)); + s16 val; + std::memcpy(&val, buf.data(), sizeof(val)); + EXPECT_EQ(val, -1000); +} + +TEST(BinarySpanWriter, WriteU32) +{ + std::array buf = {}; + BinarySpanWriter writer(buf); + EXPECT_TRUE(writer.WriteU32(0x12345678)); + EXPECT_EQ(buf[0], 0x78u); + EXPECT_EQ(buf[1], 0x56u); + EXPECT_EQ(buf[2], 0x34u); + EXPECT_EQ(buf[3], 0x12u); +} + +TEST(BinarySpanWriter, WriteS32) +{ + std::array buf = {}; + BinarySpanWriter writer(buf); + EXPECT_TRUE(writer.WriteS32(-123456789)); + s32 val; + std::memcpy(&val, buf.data(), sizeof(val)); + EXPECT_EQ(val, -123456789); +} + +TEST(BinarySpanWriter, WriteU64) +{ + std::array buf = {}; + BinarySpanWriter writer(buf); + EXPECT_TRUE(writer.WriteU64(0x0123456789ABCDEFull)); + EXPECT_EQ(buf[0], 0xEFu); + EXPECT_EQ(buf[7], 0x01u); +} + +TEST(BinarySpanWriter, WriteS64) +{ + std::array buf = {}; + BinarySpanWriter writer(buf); + EXPECT_TRUE(writer.WriteS64(-1234567890123456789ll)); + s64 val; + std::memcpy(&val, buf.data(), sizeof(val)); + EXPECT_EQ(val, -1234567890123456789ll); +} + +TEST(BinarySpanWriter, WriteFloat) +{ + std::array buf = {}; + BinarySpanWriter writer(buf); + EXPECT_TRUE(writer.WriteFloat(1.0f)); + float val; + std::memcpy(&val, buf.data(), sizeof(val)); + EXPECT_FLOAT_EQ(val, 1.0f); +} + +TEST(BinarySpanWriter, WriteBool) +{ + std::array buf = {}; + BinarySpanWriter writer(buf); + EXPECT_TRUE(writer.WriteBool(false)); + EXPECT_TRUE(writer.WriteBool(true)); + EXPECT_EQ(buf[0], 0x00u); + EXPECT_EQ(buf[1], 0x01u); +} + +TEST(BinarySpanWriter, WriteCString) +{ + std::array buf = {}; + BinarySpanWriter writer(buf); + EXPECT_TRUE(writer.WriteCString("Hello")); + EXPECT_EQ(buf[0], 'H'); + EXPECT_EQ(buf[4], 'o'); + EXPECT_EQ(buf[5], '\0'); + EXPECT_EQ(writer.GetBufferWritten(), 6u); +} + +TEST(BinarySpanWriter, WriteCStringEmpty) +{ + std::array buf = {}; + BinarySpanWriter writer(buf); + EXPECT_TRUE(writer.WriteCString("")); + EXPECT_EQ(buf[0], '\0'); + EXPECT_EQ(writer.GetBufferWritten(), 1u); +} + +TEST(BinarySpanWriter, WriteSizePrefixedString) +{ + std::array buf = {}; + BinarySpanWriter writer(buf); + EXPECT_TRUE(writer.WriteSizePrefixedString("Test")); + + u32 length; + std::memcpy(&length, buf.data(), sizeof(length)); + EXPECT_EQ(length, 4u); + EXPECT_EQ(buf[4], 'T'); + EXPECT_EQ(buf[7], 't'); + EXPECT_EQ(writer.GetBufferWritten(), 8u); +} + +TEST(BinarySpanWriter, WriteSizePrefixedStringEmpty) +{ + std::array buf = {}; + BinarySpanWriter writer(buf); + EXPECT_TRUE(writer.WriteSizePrefixedString("")); + + u32 length; + std::memcpy(&length, buf.data(), sizeof(length)); + EXPECT_EQ(length, 0u); + EXPECT_EQ(writer.GetBufferWritten(), 4u); +} + +TEST(BinarySpanWriter, StreamOperators) +{ + std::array buf = {}; + BinarySpanWriter writer(buf); + + writer << static_cast(0x01) << static_cast(0xFE) << static_cast(0x1234) << static_cast(0xABCD) + << static_cast(0x12345678) << static_cast(0xEDCBA988) << static_cast(0x0123456789ABCDEFull) + << 1.0f; + + EXPECT_EQ(writer.GetBufferWritten(), 26u); + + // Verify by reading back + BinarySpanReader reader(std::span(buf.data(), writer.GetBufferWritten())); + EXPECT_EQ(reader.ReadU8(), 0x01u); + EXPECT_EQ(reader.ReadS8(), static_cast(0xFE)); + EXPECT_EQ(reader.ReadU16(), 0x1234u); + EXPECT_EQ(reader.ReadS16(), static_cast(0xABCD)); + EXPECT_EQ(reader.ReadU32(), 0x12345678u); + EXPECT_EQ(reader.ReadS32(), static_cast(0xEDCBA988)); + EXPECT_EQ(reader.ReadU64(), 0x0123456789ABCDEFull); + EXPECT_FLOAT_EQ(reader.ReadFloat(), 1.0f); +} + +TEST(BinarySpanWriter, StreamOperatorCString) +{ + std::array buf = {}; + BinarySpanWriter writer(buf); + writer << "Hello"sv; + + BinarySpanReader reader(std::span(buf.data(), writer.GetBufferWritten())); + EXPECT_EQ(reader.ReadCString(), "Hello"sv); +} + +TEST(BinarySpanWriter, WriteOptionalTWithValue) +{ + std::array buf = {}; + BinarySpanWriter writer(buf); + std::optional val = 0x12345678u; + EXPECT_TRUE(writer.WriteOptionalT(val)); + EXPECT_EQ(writer.GetBufferWritten(), 5u); + + BinarySpanReader reader(std::span(buf.data(), writer.GetBufferWritten())); + EXPECT_TRUE(reader.ReadBool()); + EXPECT_EQ(reader.ReadU32(), 0x12345678u); +} + +TEST(BinarySpanWriter, WriteOptionalTWithoutValue) +{ + std::array buf = {}; + BinarySpanWriter writer(buf); + std::optional val; + EXPECT_TRUE(writer.WriteOptionalT(val)); + EXPECT_EQ(writer.GetBufferWritten(), 1u); + + BinarySpanReader reader(std::span(buf.data(), writer.GetBufferWritten())); + EXPECT_FALSE(reader.ReadBool()); +} + +TEST(BinarySpanWriter, GetRemainingSpan) +{ + std::array buf = {}; + BinarySpanWriter writer(buf); + writer.IncrementPosition(10); + auto remaining = writer.GetRemainingSpan(); + EXPECT_EQ(remaining.size(), buf.size() - 10); +} + +TEST(BinarySpanWriter, GetRemainingSpanWithSize) +{ + std::array buf = {}; + BinarySpanWriter writer(buf); + writer.IncrementPosition(10); + auto remaining = writer.GetRemainingSpan(5); + EXPECT_EQ(remaining.size(), 5u); +} + +TEST(BinarySpanWriter, WriteBeyondBuffer) +{ + std::array buf = {}; + BinarySpanWriter writer(buf); + EXPECT_FALSE(writer.WriteU32(0x12345678)); +} + +TEST(BinarySpanWriter, WriteCStringBeyondBuffer) +{ + std::array buf = {}; + BinarySpanWriter writer(buf); + EXPECT_FALSE(writer.WriteCString("Hello")); // needs 6 bytes +} + +TEST(BinarySpanWriter, WriteSizePrefixedStringBeyondBuffer) +{ + std::array buf = {}; + BinarySpanWriter writer(buf); + EXPECT_FALSE(writer.WriteSizePrefixedString("Test")); // needs 8 bytes +} + +////////////////////////////////////////////////////////////////////////// +// Round-trip Tests (Writer -> Reader) +////////////////////////////////////////////////////////////////////////// + +TEST(BinarySpanRoundTrip, AllPrimitiveTypes) +{ + std::array buf = {}; + BinarySpanWriter writer(buf); + + writer.WriteU8(0xAB); + writer.WriteS8(-42); + writer.WriteU16(0x1234); + writer.WriteS16(-1000); + writer.WriteU32(0xDEADBEEF); + writer.WriteS32(-123456789); + writer.WriteU64(0xFEDCBA9876543210ull); + writer.WriteS64(-876543210123456789ll); + writer.WriteFloat(3.14159f); + writer.WriteBool(true); + + BinarySpanReader reader(std::span(buf.data(), writer.GetBufferWritten())); + EXPECT_EQ(reader.ReadU8(), 0xABu); + EXPECT_EQ(reader.ReadS8(), -42); + EXPECT_EQ(reader.ReadU16(), 0x1234u); + EXPECT_EQ(reader.ReadS16(), -1000); + EXPECT_EQ(reader.ReadU32(), 0xDEADBEEFu); + EXPECT_EQ(reader.ReadS32(), -123456789); + EXPECT_EQ(reader.ReadU64(), 0xFEDCBA9876543210ull); + EXPECT_EQ(reader.ReadS64(), -876543210123456789ll); + EXPECT_FLOAT_EQ(reader.ReadFloat(), 3.14159f); + EXPECT_TRUE(reader.ReadBool()); +} + +TEST(BinarySpanRoundTrip, CStrings) +{ + std::array buf = {}; + BinarySpanWriter writer(buf); + + writer.WriteCString("Hello"); + writer.WriteCString(""); + writer.WriteCString("World!"); + + BinarySpanReader reader(std::span(buf.data(), writer.GetBufferWritten())); + EXPECT_EQ(reader.ReadCString(), "Hello"sv); + EXPECT_EQ(reader.ReadCString(), ""sv); + EXPECT_EQ(reader.ReadCString(), "World!"sv); +} + +TEST(BinarySpanRoundTrip, SizePrefixedStrings) +{ + std::array buf = {}; + BinarySpanWriter writer(buf); + + writer.WriteSizePrefixedString("Testing"); + writer.WriteSizePrefixedString(""); + writer.WriteSizePrefixedString("123"); + + BinarySpanReader reader(std::span(buf.data(), writer.GetBufferWritten())); + EXPECT_EQ(reader.ReadSizePrefixedString(), "Testing"sv); + EXPECT_EQ(reader.ReadSizePrefixedString(), ""sv); + EXPECT_EQ(reader.ReadSizePrefixedString(), "123"sv); +} + +TEST(BinarySpanRoundTrip, OptionalValues) +{ + std::array buf = {}; + BinarySpanWriter writer(buf); + + std::optional val1 = 12345u; + std::optional val2; + std::optional val3 = u16(9999); + + writer.WriteOptionalT(val1); + writer.WriteOptionalT(val2); + writer.WriteOptionalT(val3); + + BinarySpanReader reader(std::span(buf.data(), writer.GetBufferWritten())); + + std::optional read1, read2; + std::optional read3; + + EXPECT_TRUE(reader.ReadOptionalT(&read1)); + EXPECT_TRUE(reader.ReadOptionalT(&read2)); + EXPECT_TRUE(reader.ReadOptionalT(&read3)); + + EXPECT_TRUE(read1.has_value()); + EXPECT_EQ(read1.value(), 12345u); + EXPECT_FALSE(read2.has_value()); + EXPECT_TRUE(read3.has_value()); + EXPECT_EQ(read3.value(), 9999u); +} + +TEST(BinarySpanRoundTrip, MixedContent) +{ + std::array buf = {}; + BinarySpanWriter writer(buf); + + writer.WriteU32(0x12345678); + writer.WriteCString("Header"); + writer.WriteU16(100); + writer.WriteSizePrefixedString("Payload"); + writer.WriteFloat(2.5f); + writer.WriteBool(false); + + BinarySpanReader reader(std::span(buf.data(), writer.GetBufferWritten())); + + EXPECT_EQ(reader.ReadU32(), 0x12345678u); + EXPECT_EQ(reader.ReadCString(), "Header"sv); + EXPECT_EQ(reader.ReadU16(), 100u); + EXPECT_EQ(reader.ReadSizePrefixedString(), "Payload"sv); + EXPECT_FLOAT_EQ(reader.ReadFloat(), 2.5f); + EXPECT_FALSE(reader.ReadBool()); +} \ No newline at end of file diff --git a/src/common-tests/common-tests.vcxproj b/src/common-tests/common-tests.vcxproj index d88d87a72..0f2378691 100644 --- a/src/common-tests/common-tests.vcxproj +++ b/src/common-tests/common-tests.vcxproj @@ -3,12 +3,15 @@ + + + diff --git a/src/common-tests/common-tests.vcxproj.filters b/src/common-tests/common-tests.vcxproj.filters index 737de39e2..9a9648649 100644 --- a/src/common-tests/common-tests.vcxproj.filters +++ b/src/common-tests/common-tests.vcxproj.filters @@ -10,5 +10,8 @@ + + + \ No newline at end of file diff --git a/src/common-tests/heap_array_tests.cpp b/src/common-tests/heap_array_tests.cpp new file mode 100644 index 000000000..ca7645ef0 --- /dev/null +++ b/src/common-tests/heap_array_tests.cpp @@ -0,0 +1,827 @@ +// SPDX-FileCopyrightText: 2019-2025 Connor McLaughlin +// SPDX-License-Identifier: CC-BY-NC-ND-4.0 + +#include "common/heap_array.h" + +#include + +// ============================================================================ +// FixedHeapArray Tests +// ============================================================================ + +TEST(FixedHeapArray, DefaultConstruction) +{ + FixedHeapArray arr; + EXPECT_EQ(arr.size(), 10u); + EXPECT_EQ(arr.capacity(), 10u); + EXPECT_EQ(arr.size_bytes(), 10u * sizeof(int)); + EXPECT_FALSE(arr.empty()); + EXPECT_NE(arr.data(), nullptr); +} + +TEST(FixedHeapArray, CopyConstruction) +{ + FixedHeapArray arr1; + for (size_t i = 0; i < arr1.size(); ++i) + arr1[i] = static_cast(i * 10); + + FixedHeapArray arr2(arr1); + EXPECT_EQ(arr2.size(), arr1.size()); + for (size_t i = 0; i < arr1.size(); ++i) + EXPECT_EQ(arr2[i], arr1[i]); + + // Ensure they have separate storage + EXPECT_NE(arr1.data(), arr2.data()); +} + +TEST(FixedHeapArray, MoveConstruction) +{ + FixedHeapArray arr1; + int* original_data = arr1.data(); + for (size_t i = 0; i < arr1.size(); ++i) + arr1[i] = static_cast(i * 10); + + FixedHeapArray arr2(std::move(arr1)); + EXPECT_EQ(arr2.data(), original_data); + EXPECT_EQ(arr2.size(), 5u); + + for (size_t i = 0; i < arr2.size(); ++i) + EXPECT_EQ(arr2[i], static_cast(i * 10)); +} + +TEST(FixedHeapArray, ElementAccess) +{ + FixedHeapArray arr; + arr[0] = 100; + arr[1] = 200; + arr[2] = 300; + arr[3] = 400; + arr[4] = 500; + + EXPECT_EQ(arr[0], 100); + EXPECT_EQ(arr[1], 200); + EXPECT_EQ(arr[2], 300); + EXPECT_EQ(arr[3], 400); + EXPECT_EQ(arr[4], 500); + + const auto& carr = arr; + EXPECT_EQ(carr[0], 100); + EXPECT_EQ(carr[4], 500); +} + +TEST(FixedHeapArray, FrontBack) +{ + FixedHeapArray arr; + arr[0] = 10; + arr[4] = 50; + + EXPECT_EQ(arr.front(), 10); + EXPECT_EQ(arr.back(), 50); + + arr.front() = 15; + arr.back() = 55; + EXPECT_EQ(arr[0], 15); + EXPECT_EQ(arr[4], 55); + + const auto& carr = arr; + EXPECT_EQ(carr.front(), 15); + EXPECT_EQ(carr.back(), 55); +} + +TEST(FixedHeapArray, Iterators) +{ + FixedHeapArray arr; + for (size_t i = 0; i < arr.size(); ++i) + arr[i] = static_cast(i); + + int expected = 0; + for (auto it = arr.begin(); it != arr.end(); ++it) + { + EXPECT_EQ(*it, expected); + ++expected; + } + EXPECT_EQ(expected, 5); + + expected = 0; + for (auto it = arr.cbegin(); it != arr.cend(); ++it) + { + EXPECT_EQ(*it, expected); + ++expected; + } +} + +TEST(FixedHeapArray, Fill) +{ + FixedHeapArray arr; + arr.fill(42); + + for (size_t i = 0; i < arr.size(); ++i) + EXPECT_EQ(arr[i], 42); +} + +TEST(FixedHeapArray, Swap) +{ + FixedHeapArray arr1; + FixedHeapArray arr2; + arr1.fill(10); + arr2.fill(20); + + int* arr1_data = arr1.data(); + int* arr2_data = arr2.data(); + + arr1.swap(arr2); + + EXPECT_EQ(arr1.data(), arr2_data); + EXPECT_EQ(arr2.data(), arr1_data); + + for (size_t i = 0; i < arr1.size(); ++i) + { + EXPECT_EQ(arr1[i], 20); + EXPECT_EQ(arr2[i], 10); + } +} + +TEST(FixedHeapArray, Span) +{ + FixedHeapArray arr; + for (size_t i = 0; i < arr.size(); ++i) + arr[i] = static_cast(i * 2); + + std::span sp = arr.span(); + EXPECT_EQ(sp.size(), 5u); + EXPECT_EQ(sp.data(), arr.data()); + + for (size_t i = 0; i < sp.size(); ++i) + EXPECT_EQ(sp[i], static_cast(i * 2)); + + const auto& carr = arr; + std::span csp = carr.cspan(); + EXPECT_EQ(csp.size(), 5u); + EXPECT_EQ(csp.data(), carr.data()); +} + +TEST(FixedHeapArray, MoveAssignment) +{ + FixedHeapArray arr1; + arr1.fill(42); + int* original_data = arr1.data(); + + FixedHeapArray arr2; + arr2.fill(0); + + arr2 = std::move(arr1); + EXPECT_EQ(arr2.data(), original_data); + for (size_t i = 0; i < arr2.size(); ++i) + EXPECT_EQ(arr2[i], 42); +} + +TEST(FixedHeapArray, AlignedAllocation) +{ + constexpr size_t alignment = 64; + FixedHeapArray arr; + + uintptr_t addr = reinterpret_cast(arr.data()); + EXPECT_EQ(addr % alignment, 0u); +} + +TEST(FixedHeapArray, DifferentTypes) +{ + FixedHeapArray arr; + arr[0] = 1.5; + arr[1] = 2.5; + arr[2] = 3.5; + + EXPECT_DOUBLE_EQ(arr[0], 1.5); + EXPECT_DOUBLE_EQ(arr[1], 2.5); + EXPECT_DOUBLE_EQ(arr[2], 3.5); + EXPECT_EQ(arr.size_bytes(), 3u * sizeof(double)); +} + +// ============================================================================ +// DynamicHeapArray Tests +// ============================================================================ + +TEST(DynamicHeapArray, DefaultConstruction) +{ + DynamicHeapArray arr; + EXPECT_EQ(arr.size(), 0u); + EXPECT_EQ(arr.capacity(), 0u); + EXPECT_EQ(arr.size_bytes(), 0u); + EXPECT_TRUE(arr.empty()); + EXPECT_EQ(arr.data(), nullptr); +} + +TEST(DynamicHeapArray, SizeConstruction) +{ + DynamicHeapArray arr(10); + EXPECT_EQ(arr.size(), 10u); + EXPECT_EQ(arr.capacity(), 10u); + EXPECT_EQ(arr.size_bytes(), 10u * sizeof(int)); + EXPECT_FALSE(arr.empty()); + EXPECT_NE(arr.data(), nullptr); +} + +TEST(DynamicHeapArray, RangeConstructionBeginEnd) +{ + int source[] = {1, 2, 3, 4, 5}; + DynamicHeapArray arr(std::begin(source), std::end(source)); + + EXPECT_EQ(arr.size(), 5u); + for (size_t i = 0; i < arr.size(); ++i) + EXPECT_EQ(arr[i], source[i]); +} + +TEST(DynamicHeapArray, RangeConstructionBeginCount) +{ + int source[] = {10, 20, 30, 40, 50}; + DynamicHeapArray arr(source, 5); + + EXPECT_EQ(arr.size(), 5u); + for (size_t i = 0; i < arr.size(); ++i) + EXPECT_EQ(arr[i], source[i]); +} + +TEST(DynamicHeapArray, SpanConstruction) +{ + int source[] = {1, 2, 3, 4, 5}; + std::span sp(source); + DynamicHeapArray arr(sp); + + EXPECT_EQ(arr.size(), 5u); + for (size_t i = 0; i < arr.size(); ++i) + EXPECT_EQ(arr[i], source[i]); +} + +TEST(DynamicHeapArray, SpanConstructionEmpty) +{ + std::span sp; + DynamicHeapArray arr(sp); + + EXPECT_EQ(arr.size(), 0u); + EXPECT_TRUE(arr.empty()); +} + +TEST(DynamicHeapArray, CopyConstruction) +{ + DynamicHeapArray arr1(5); + for (size_t i = 0; i < arr1.size(); ++i) + arr1[i] = static_cast(i * 10); + + DynamicHeapArray arr2(arr1); + EXPECT_EQ(arr2.size(), arr1.size()); + for (size_t i = 0; i < arr1.size(); ++i) + EXPECT_EQ(arr2[i], arr1[i]); + + // Ensure they have separate storage + EXPECT_NE(arr1.data(), arr2.data()); +} + +TEST(DynamicHeapArray, CopyConstructionEmpty) +{ + DynamicHeapArray arr1; + DynamicHeapArray arr2(arr1); + + EXPECT_EQ(arr2.size(), 0u); + EXPECT_TRUE(arr2.empty()); +} + +TEST(DynamicHeapArray, MoveConstruction) +{ + DynamicHeapArray arr1(5); + int* original_data = arr1.data(); + for (size_t i = 0; i < arr1.size(); ++i) + arr1[i] = static_cast(i * 10); + + DynamicHeapArray arr2(std::move(arr1)); + + EXPECT_EQ(arr2.data(), original_data); + EXPECT_EQ(arr2.size(), 5u); + EXPECT_EQ(arr1.size(), 0u); + EXPECT_EQ(arr1.data(), nullptr); + + for (size_t i = 0; i < arr2.size(); ++i) + EXPECT_EQ(arr2[i], static_cast(i * 10)); +} + +TEST(DynamicHeapArray, ElementAccess) +{ + DynamicHeapArray arr(5); + arr[0] = 100; + arr[1] = 200; + arr[2] = 300; + arr[3] = 400; + arr[4] = 500; + + EXPECT_EQ(arr[0], 100); + EXPECT_EQ(arr[1], 200); + EXPECT_EQ(arr[2], 300); + EXPECT_EQ(arr[3], 400); + EXPECT_EQ(arr[4], 500); + + const auto& carr = arr; + EXPECT_EQ(carr[0], 100); + EXPECT_EQ(carr[4], 500); +} + +TEST(DynamicHeapArray, FrontBack) +{ + DynamicHeapArray arr(5); + arr[0] = 10; + arr[4] = 50; + + EXPECT_EQ(arr.front(), 10); + EXPECT_EQ(arr.back(), 50); + + arr.front() = 15; + arr.back() = 55; + EXPECT_EQ(arr[0], 15); + EXPECT_EQ(arr[4], 55); + + const auto& carr = arr; + EXPECT_EQ(carr.front(), 15); + EXPECT_EQ(carr.back(), 55); +} + +TEST(DynamicHeapArray, Iterators) +{ + DynamicHeapArray arr(5); + for (size_t i = 0; i < arr.size(); ++i) + arr[i] = static_cast(i); + + int expected = 0; + for (auto it = arr.begin(); it != arr.end(); ++it) + { + EXPECT_EQ(*it, expected); + ++expected; + } + EXPECT_EQ(expected, 5); + + expected = 0; + for (auto it = arr.cbegin(); it != arr.cend(); ++it) + { + EXPECT_EQ(*it, expected); + ++expected; + } +} + +TEST(DynamicHeapArray, Fill) +{ + DynamicHeapArray arr(10); + arr.fill(42); + + for (size_t i = 0; i < arr.size(); ++i) + EXPECT_EQ(arr[i], 42); +} + +TEST(DynamicHeapArray, Swap) +{ + DynamicHeapArray arr1(5); + DynamicHeapArray arr2(3); + arr1.fill(10); + arr2.fill(20); + + int* arr1_data = arr1.data(); + int* arr2_data = arr2.data(); + + arr1.swap(arr2); + + EXPECT_EQ(arr1.data(), arr2_data); + EXPECT_EQ(arr2.data(), arr1_data); + EXPECT_EQ(arr1.size(), 3u); + EXPECT_EQ(arr2.size(), 5u); + + for (size_t i = 0; i < arr1.size(); ++i) + EXPECT_EQ(arr1[i], 20); + for (size_t i = 0; i < arr2.size(); ++i) + EXPECT_EQ(arr2[i], 10); +} + +TEST(DynamicHeapArray, ResizeGrow) +{ + DynamicHeapArray arr(5); + for (size_t i = 0; i < arr.size(); ++i) + arr[i] = static_cast(i); + + arr.resize(10); + EXPECT_EQ(arr.size(), 10u); + + // Original data should be preserved + for (size_t i = 0; i < 5; ++i) + EXPECT_EQ(arr[i], static_cast(i)); +} + +TEST(DynamicHeapArray, ResizeShrink) +{ + DynamicHeapArray arr(10); + for (size_t i = 0; i < arr.size(); ++i) + arr[i] = static_cast(i); + + arr.resize(5); + EXPECT_EQ(arr.size(), 5u); + + for (size_t i = 0; i < arr.size(); ++i) + EXPECT_EQ(arr[i], static_cast(i)); +} + +TEST(DynamicHeapArray, ResizeSameSize) +{ + DynamicHeapArray arr(5); + int* original_data = arr.data(); + arr.fill(42); + + arr.resize(5); + EXPECT_EQ(arr.size(), 5u); + EXPECT_EQ(arr.data(), original_data); + + for (size_t i = 0; i < arr.size(); ++i) + EXPECT_EQ(arr[i], 42); +} + +TEST(DynamicHeapArray, Deallocate) +{ + DynamicHeapArray arr(10); + EXPECT_FALSE(arr.empty()); + + arr.deallocate(); + EXPECT_TRUE(arr.empty()); + EXPECT_EQ(arr.size(), 0u); + EXPECT_EQ(arr.data(), nullptr); +} + +TEST(DynamicHeapArray, AssignSpan) +{ + DynamicHeapArray arr; + int source[] = {1, 2, 3, 4, 5}; + std::span sp(source); + + arr.assign(sp); + EXPECT_EQ(arr.size(), 5u); + for (size_t i = 0; i < arr.size(); ++i) + EXPECT_EQ(arr[i], source[i]); +} + +TEST(DynamicHeapArray, AssignBeginEnd) +{ + DynamicHeapArray arr; + int source[] = {10, 20, 30}; + + arr.assign(std::begin(source), std::end(source)); + EXPECT_EQ(arr.size(), 3u); + for (size_t i = 0; i < arr.size(); ++i) + EXPECT_EQ(arr[i], source[i]); +} + +TEST(DynamicHeapArray, AssignBeginCount) +{ + DynamicHeapArray arr; + int source[] = {100, 200, 300, 400}; + + arr.assign(source, 4); + EXPECT_EQ(arr.size(), 4u); + for (size_t i = 0; i < arr.size(); ++i) + EXPECT_EQ(arr[i], source[i]); +} + +TEST(DynamicHeapArray, AssignCopy) +{ + DynamicHeapArray arr1(5); + for (size_t i = 0; i < arr1.size(); ++i) + arr1[i] = static_cast(i * 3); + + DynamicHeapArray arr2; + arr2.assign(arr1); + + EXPECT_EQ(arr2.size(), arr1.size()); + for (size_t i = 0; i < arr1.size(); ++i) + EXPECT_EQ(arr2[i], arr1[i]); + EXPECT_NE(arr1.data(), arr2.data()); +} + +TEST(DynamicHeapArray, AssignMove) +{ + DynamicHeapArray arr1(5); + int* original_data = arr1.data(); + for (size_t i = 0; i < arr1.size(); ++i) + arr1[i] = static_cast(i * 3); + + DynamicHeapArray arr2; + arr2.assign(std::move(arr1)); + + EXPECT_EQ(arr2.size(), 5u); + EXPECT_EQ(arr2.data(), original_data); + EXPECT_EQ(arr1.size(), 0u); + EXPECT_EQ(arr1.data(), nullptr); +} + +TEST(DynamicHeapArray, AssignEmpty) +{ + DynamicHeapArray arr(10); + arr.fill(42); + + int* empty = nullptr; + arr.assign(empty, static_cast(0)); + + EXPECT_TRUE(arr.empty()); + EXPECT_EQ(arr.size(), 0u); +} + +TEST(DynamicHeapArray, AssignSameSize) +{ + DynamicHeapArray arr(5); + arr.fill(10); + int* original_data = arr.data(); + + int source[] = {1, 2, 3, 4, 5}; + arr.assign(source, 5); + + // Should reuse existing buffer + EXPECT_EQ(arr.data(), original_data); + for (size_t i = 0; i < arr.size(); ++i) + EXPECT_EQ(arr[i], source[i]); +} + +TEST(DynamicHeapArray, Span) +{ + DynamicHeapArray arr(5); + for (size_t i = 0; i < arr.size(); ++i) + arr[i] = static_cast(i * 2); + + std::span sp = arr.span(); + EXPECT_EQ(sp.size(), 5u); + EXPECT_EQ(sp.data(), arr.data()); + + for (size_t i = 0; i < sp.size(); ++i) + EXPECT_EQ(sp[i], static_cast(i * 2)); +} + +TEST(DynamicHeapArray, SpanWithOffset) +{ + DynamicHeapArray arr(10); + for (size_t i = 0; i < arr.size(); ++i) + arr[i] = static_cast(i); + + std::span sp = arr.span(3, 4); + EXPECT_EQ(sp.size(), 4u); + EXPECT_EQ(sp.data(), arr.data() + 3); + EXPECT_EQ(sp[0], 3); + EXPECT_EQ(sp[3], 6); +} + +TEST(DynamicHeapArray, SpanWithOffsetClamp) +{ + DynamicHeapArray arr(10); + for (size_t i = 0; i < arr.size(); ++i) + arr[i] = static_cast(i); + + // Request more than available + std::span sp = arr.span(7, 100); + EXPECT_EQ(sp.size(), 3u); + EXPECT_EQ(sp[0], 7); + EXPECT_EQ(sp[2], 9); +} + +TEST(DynamicHeapArray, SpanWithOffsetOutOfRange) +{ + DynamicHeapArray arr(5); + std::span sp = arr.span(10); + EXPECT_TRUE(sp.empty()); +} + +TEST(DynamicHeapArray, CSpan) +{ + DynamicHeapArray arr(5); + for (size_t i = 0; i < arr.size(); ++i) + arr[i] = static_cast(i * 2); + + const auto& carr = arr; + std::span csp = carr.cspan(); + EXPECT_EQ(csp.size(), 5u); + EXPECT_EQ(csp.data(), carr.data()); +} + +TEST(DynamicHeapArray, CSpanWithOffset) +{ + DynamicHeapArray arr(10); + for (size_t i = 0; i < arr.size(); ++i) + arr[i] = static_cast(i); + + const auto& carr = arr; + std::span csp = carr.cspan(2, 3); + EXPECT_EQ(csp.size(), 3u); + EXPECT_EQ(csp[0], 2); + EXPECT_EQ(csp[2], 4); +} + +TEST(DynamicHeapArray, CopyAssignment) +{ + DynamicHeapArray arr1(5); + for (size_t i = 0; i < arr1.size(); ++i) + arr1[i] = static_cast(i * 2); + + DynamicHeapArray arr2; + arr2 = arr1; + + EXPECT_EQ(arr2.size(), arr1.size()); + EXPECT_NE(arr2.data(), arr1.data()); + for (size_t i = 0; i < arr1.size(); ++i) + EXPECT_EQ(arr2[i], arr1[i]); +} + +TEST(DynamicHeapArray, MoveAssignment) +{ + DynamicHeapArray arr1(5); + int* original_data = arr1.data(); + arr1.fill(42); + + DynamicHeapArray arr2; + arr2 = std::move(arr1); + + EXPECT_EQ(arr2.data(), original_data); + EXPECT_EQ(arr2.size(), 5u); + EXPECT_EQ(arr1.size(), 0u); + EXPECT_EQ(arr1.data(), nullptr); + + for (size_t i = 0; i < arr2.size(); ++i) + EXPECT_EQ(arr2[i], 42); +} + +TEST(DynamicHeapArray, AlignedAllocation) +{ + constexpr size_t alignment = 64; + DynamicHeapArray arr(16); + + uintptr_t addr = reinterpret_cast(arr.data()); + EXPECT_EQ(addr % alignment, 0u); +} + +TEST(DynamicHeapArray, AlignedResize) +{ + constexpr size_t alignment = 64; + DynamicHeapArray arr(8); + arr.fill(42); + + arr.resize(32); + + uintptr_t addr = reinterpret_cast(arr.data()); + EXPECT_EQ(addr % alignment, 0u); + + // Original data preserved + for (size_t i = 0; i < 8; ++i) + EXPECT_EQ(arr[i], 42); +} + +TEST(DynamicHeapArray, DifferentTypes) +{ + DynamicHeapArray arr(3); + arr[0] = 1.5; + arr[1] = 2.5; + arr[2] = 3.5; + + EXPECT_DOUBLE_EQ(arr[0], 1.5); + EXPECT_DOUBLE_EQ(arr[1], 2.5); + EXPECT_DOUBLE_EQ(arr[2], 3.5); + EXPECT_EQ(arr.size_bytes(), 3u * sizeof(double)); +} + +TEST(DynamicHeapArray, RangeBasedFor) +{ + DynamicHeapArray arr(5); + int val = 0; + for (auto& elem : arr) + elem = val++; + + val = 0; + for (const auto& elem : arr) + { + EXPECT_EQ(elem, val); + ++val; + } +} + +TEST(DynamicHeapArray, ByteArray) +{ + DynamicHeapArray arr(16); + for (size_t i = 0; i < arr.size(); ++i) + arr[i] = static_cast(i); + + EXPECT_EQ(arr.size(), 16u); + EXPECT_EQ(arr.size_bytes(), 16u); + + for (size_t i = 0; i < arr.size(); ++i) + EXPECT_EQ(arr[i], static_cast(i)); +} + +TEST(DynamicHeapArray, EmptyRangeConstruction) +{ + int* empty = nullptr; + DynamicHeapArray arr(empty, empty); + + EXPECT_TRUE(arr.empty()); + EXPECT_EQ(arr.size(), 0u); + EXPECT_EQ(arr.data(), nullptr); +} + +TEST(DynamicHeapArray, EmptyCountConstruction) +{ + int* ptr = nullptr; + DynamicHeapArray arr(ptr, static_cast(0)); + + EXPECT_TRUE(arr.empty()); + EXPECT_EQ(arr.size(), 0u); + EXPECT_EQ(arr.data(), nullptr); +} + +TEST(FixedHeapArray, CopyAssignmentCopiesWrongDirection) +{ + FixedHeapArray src; + FixedHeapArray dst; + + src[0] = 1; + src[1] = 2; + src[2] = 3; + src[3] = 4; + dst[0] = 0; + dst[1] = 0; + dst[2] = 0; + dst[3] = 0; + + dst = src; // Should copy src -> dst + + // After assignment, dst should have src's values + EXPECT_EQ(dst[0], 1); + EXPECT_EQ(dst[1], 2); + EXPECT_EQ(dst[2], 3); + EXPECT_EQ(dst[3], 4); +} + +TEST(FixedHeapArray, EqualityOperatorMissingReturnTrue) +{ + FixedHeapArray a; + FixedHeapArray b; + + a[0] = 1; + a[1] = 2; + a[2] = 3; + a[3] = 4; + b[0] = 1; + b[1] = 2; + b[2] = 3; + b[3] = 4; + + // Both arrays have identical content, should return true + EXPECT_TRUE(a == b); +} + +TEST(FixedHeapArray, InequalityOperatorReturnTrue) +{ + FixedHeapArray a; + FixedHeapArray b; + + a[0] = 1; + a[1] = 2; + a[2] = 3; + a[3] = 4; + b[0] = 1; + b[1] = 2; + b[2] = 3; + b[3] = 5; // Last element differs + + // Arrays differ, inequality should return true + EXPECT_TRUE(a != b); +} + +TEST(DynamicHeapArray, EqualityOperatorReturnTrue) +{ + DynamicHeapArray a(4); + DynamicHeapArray b(4); + + a[0] = 1; + a[1] = 2; + a[2] = 3; + a[3] = 4; + b[0] = 1; + b[1] = 2; + b[2] = 3; + b[3] = 4; + + // Both arrays have identical content, should return true + EXPECT_TRUE(a == b); +} + +TEST(DynamicHeapArray, EqualityOperatorWhenSizesDiffer) +{ + DynamicHeapArray a(4); + DynamicHeapArray b(8); + + // Different sizes should NOT be equal + EXPECT_FALSE(a == b); +} + +TEST(DynamicHeapArray, InequalityOperatorWhenSizesDiffer) +{ + DynamicHeapArray a(4); + DynamicHeapArray b(8); + + // Different sizes should be not-equal + EXPECT_TRUE(a != b); +} diff --git a/src/common-tests/small_string_tests.cpp b/src/common-tests/small_string_tests.cpp new file mode 100644 index 000000000..ccba63e03 --- /dev/null +++ b/src/common-tests/small_string_tests.cpp @@ -0,0 +1,1529 @@ +// SPDX-FileCopyrightText: 2019-2026 Connor McLaughlin +// SPDX-License-Identifier: CC-BY-NC-ND-4.0 + +#include "common/small_string.h" + +#include +#include +#include +#include + +using namespace std::string_view_literals; + +////////////////////////////////////////////////////////////////////////// +// SmallStringBase Test Suite +////////////////////////////////////////////////////////////////////////// + +// Constructor Tests +TEST(SmallStringBase, DefaultConstructor) +{ + SmallString s; + EXPECT_TRUE(s.empty()); + EXPECT_EQ(s.length(), 0u); + EXPECT_STREQ(s.c_str(), ""); +} + +TEST(SmallStringBase, ConstCharConstructor) +{ + SmallString s("Hello"); + EXPECT_FALSE(s.empty()); + EXPECT_EQ(s.length(), 5u); + EXPECT_STREQ(s.c_str(), "Hello"); +} + +TEST(SmallStringBase, ConstCharWithLengthConstructor) +{ + SmallString s("Hello World", 5); + EXPECT_EQ(s.length(), 5u); + EXPECT_STREQ(s.c_str(), "Hello"); +} + +TEST(SmallStringBase, CopyConstructor) +{ + SmallString original("Test String"); + SmallString copy(original); + EXPECT_EQ(copy.length(), original.length()); + EXPECT_STREQ(copy.c_str(), original.c_str()); +} + +TEST(SmallStringBase, MoveConstructor) +{ + SmallString original("Test String"); + SmallString moved(std::move(original)); + EXPECT_EQ(moved.length(), 11u); + EXPECT_STREQ(moved.c_str(), "Test String"); +} + +TEST(SmallStringBase, StdStringConstructor) +{ + std::string stdStr = "Hello from std::string"; + SmallString s(stdStr); + EXPECT_EQ(s.length(), stdStr.length()); + EXPECT_STREQ(s.c_str(), stdStr.c_str()); +} + +TEST(SmallStringBase, StringViewConstructor) +{ + std::string_view sv = "Hello from string_view"; + SmallString s(sv); + EXPECT_EQ(s.length(), sv.length()); + EXPECT_EQ(s.view(), sv); +} + +// Assignment Tests +TEST(SmallStringBase, AssignConstChar) +{ + SmallString str; + str.assign("New Value"); + EXPECT_EQ(str.length(), 9u); + EXPECT_STREQ(str.c_str(), "New Value"); +} + +TEST(SmallStringBase, AssignConstCharWithLength) +{ + SmallString str; + str.assign("New Value Extended", 9); + EXPECT_EQ(str.length(), 9u); + EXPECT_STREQ(str.c_str(), "New Value"); +} + +TEST(SmallStringBase, AssignStdString) +{ + SmallString str; + std::string stdStr = "std::string value"; + str.assign(stdStr); + EXPECT_STREQ(str.c_str(), stdStr.c_str()); +} + +TEST(SmallStringBase, AssignStringView) +{ + SmallString str; + std::string_view sv = "string_view value"sv; + str.assign(sv); + EXPECT_EQ(str.view(), sv); +} + +TEST(SmallStringBase, AssignSmallStringBase) +{ + SmallString str; + SmallString other("Other string"); + str.assign(other); + EXPECT_STREQ(str.c_str(), other.c_str()); +} + +TEST(SmallStringBase, AssignMove) +{ + SmallString str; + SmallString other("Move this"); + str.assign(std::move(other)); + EXPECT_STREQ(str.c_str(), "Move this"); +} + +// Clear Tests +TEST(SmallStringBase, Clear) +{ + SmallString str("Some content"); + EXPECT_FALSE(str.empty()); + str.clear(); + EXPECT_TRUE(str.empty()); + EXPECT_EQ(str.length(), 0u); +} + +// Append Tests +TEST(SmallStringBase, AppendChar) +{ + SmallString str("Hello"); + str.append('!'); + EXPECT_STREQ(str.c_str(), "Hello!"); +} + +TEST(SmallStringBase, AppendConstChar) +{ + SmallString str("Hello"); + str.append(" World"); + EXPECT_STREQ(str.c_str(), "Hello World"); +} + +TEST(SmallStringBase, AppendConstCharWithLength) +{ + SmallString str("Hello"); + str.append(" World!!!", 6); + EXPECT_STREQ(str.c_str(), "Hello World"); +} + +TEST(SmallStringBase, AppendStdString) +{ + SmallString str("Hello"); + std::string suffix = " World"; + str.append(suffix); + EXPECT_STREQ(str.c_str(), "Hello World"); +} + +TEST(SmallStringBase, AppendStringView) +{ + SmallString str("Hello"); + str.append(" World"sv); + EXPECT_STREQ(str.c_str(), "Hello World"); +} + +TEST(SmallStringBase, AppendSmallStringBase) +{ + SmallString str("Hello"); + SmallString suffix(" World"); + str.append(suffix); + EXPECT_STREQ(str.c_str(), "Hello World"); +} + +TEST(SmallStringBase, AppendSprintf) +{ + SmallString str("Value: "); + str.append_sprintf("%d", 42); + EXPECT_STREQ(str.c_str(), "Value: 42"); +} + +TEST(SmallStringBase, AppendFormat) +{ + SmallString str("Value: "); + str.append_format("{}", 42); + EXPECT_STREQ(str.c_str(), "Value: 42"); +} + +TEST(SmallStringBase, AppendHex) +{ + SmallString str; + const u8 data[] = {0xDE, 0xAD, 0xBE, 0xEF}; + str.append_hex(data, sizeof(data)); + EXPECT_STREQ(str.c_str(), "deadbeef"); +} + +TEST(SmallStringBase, AppendHexCommaSeparated) +{ + SmallString str; + const u8 data[] = {0xDE, 0xAD}; + str.append_hex(data, sizeof(data), true); + EXPECT_STREQ(str.c_str(), "0xde, 0xad"); +} + +// Prepend Tests +TEST(SmallStringBase, PrependChar) +{ + SmallString str("ello"); + str.prepend('H'); + EXPECT_STREQ(str.c_str(), "Hello"); +} + +TEST(SmallStringBase, PrependConstChar) +{ + SmallString str("World"); + str.prepend("Hello "); + EXPECT_STREQ(str.c_str(), "Hello World"); +} + +TEST(SmallStringBase, PrependConstCharWithLength) +{ + SmallString str("World"); + str.prepend("Hello XXX", 6); + EXPECT_STREQ(str.c_str(), "Hello World"); +} + +TEST(SmallStringBase, PrependStdString) +{ + SmallString str("World"); + std::string prefix = "Hello "; + str.prepend(prefix); + EXPECT_STREQ(str.c_str(), "Hello World"); +} + +TEST(SmallStringBase, PrependStringView) +{ + SmallString str("World"); + str.prepend("Hello "sv); + EXPECT_STREQ(str.c_str(), "Hello World"); +} + +TEST(SmallStringBase, PrependSmallStringBase) +{ + SmallString str("World"); + SmallString prefix("Hello "); + str.prepend(prefix); + EXPECT_STREQ(str.c_str(), "Hello World"); +} + +TEST(SmallStringBase, PrependSprintf) +{ + SmallString str(" items"); + str.prepend_sprintf("%d", 5); + EXPECT_STREQ(str.c_str(), "5 items"); +} + +TEST(SmallStringBase, PrependFormat) +{ + SmallString str(" items"); + str.prepend_format("{}", 5); + EXPECT_STREQ(str.c_str(), "5 items"); +} + +// Insert Tests +TEST(SmallStringBase, InsertConstChar) +{ + SmallString str("Hello World"); + str.insert(5, " Beautiful"); + EXPECT_STREQ(str.c_str(), "Hello Beautiful World"); +} + +TEST(SmallStringBase, InsertConstCharWithLength) +{ + SmallString str("Hello World"); + str.insert(5, " BeautifulXXX", 10); + EXPECT_STREQ(str.c_str(), "Hello Beautiful World"); +} + +TEST(SmallStringBase, InsertStdString) +{ + SmallString str("Hello World"); + std::string insert = " Beautiful"; + str.insert(5, insert); + EXPECT_STREQ(str.c_str(), "Hello Beautiful World"); +} + +TEST(SmallStringBase, InsertStringView) +{ + SmallString str("Hello World"); + str.insert(5, " Beautiful"sv); + EXPECT_STREQ(str.c_str(), "Hello Beautiful World"); +} + +TEST(SmallStringBase, InsertSmallStringBase) +{ + SmallString str("Hello World"); + SmallString insert(" Beautiful"); + str.insert(5, insert); + EXPECT_STREQ(str.c_str(), "Hello Beautiful World"); +} + +TEST(SmallStringBase, InsertNegativeOffset) +{ + SmallString str("Hello World"); + str.insert(-6, "Beautiful "); + EXPECT_STREQ(str.c_str(), "HelloBeautiful World"); +} + +// Format Tests +TEST(SmallStringBase, Sprintf) +{ + SmallString str; + str.sprintf("Value: %d, String: %s", 42, "test"); + EXPECT_STREQ(str.c_str(), "Value: 42, String: test"); +} + +TEST(SmallStringBase, Format) +{ + SmallString str; + str.format("Value: {}, String: {}", 42, "test"); + EXPECT_STREQ(str.c_str(), "Value: 42, String: test"); +} + +// Comparison Tests - equals +TEST(SmallStringBase, EqualsConstChar) +{ + SmallString str("Hello"); + EXPECT_TRUE(str.equals("Hello")); + EXPECT_FALSE(str.equals("hello")); + EXPECT_FALSE(str.equals("Hello World")); +} + +TEST(SmallStringBase, EqualsSmallStringBase) +{ + SmallString str("Hello"); + SmallString other("Hello"); + SmallString different("World"); + EXPECT_TRUE(str.equals(other)); + EXPECT_FALSE(str.equals(different)); +} + +TEST(SmallStringBase, EqualsStringView) +{ + SmallString str("Hello"); + EXPECT_TRUE(str.equals("Hello"sv)); + EXPECT_FALSE(str.equals("hello"sv)); +} + +TEST(SmallStringBase, EqualsStdString) +{ + SmallString str("Hello"); + std::string same = "Hello"; + std::string different = "World"; + EXPECT_TRUE(str.equals(same)); + EXPECT_FALSE(str.equals(different)); +} + +TEST(SmallStringBase, EqualsEmptyString) +{ + SmallString str; + EXPECT_TRUE(str.equals("")); + EXPECT_FALSE(str.equals("x")); +} + +// Comparison Tests - iequals (case insensitive) +TEST(SmallStringBase, IEqualsConstChar) +{ + SmallString str("Hello"); + EXPECT_TRUE(str.iequals("hello")); + EXPECT_TRUE(str.iequals("HELLO")); + EXPECT_TRUE(str.iequals("HeLLo")); + EXPECT_FALSE(str.iequals("World")); +} + +TEST(SmallStringBase, IEqualsStringView) +{ + SmallString str("Hello"); + EXPECT_TRUE(str.iequals("hello"sv)); + EXPECT_TRUE(str.iequals("HELLO"sv)); +} + +TEST(SmallStringBase, IEqualsStdString) +{ + SmallString str("Hello"); + std::string same = "hello"; + EXPECT_TRUE(str.iequals(same)); +} + +// Comparison Tests - compare +TEST(SmallStringBase, CompareConstChar) +{ + SmallString str("banana"); + EXPECT_LT(str.compare("cherry"), 0); + EXPECT_GT(str.compare("apple"), 0); + EXPECT_EQ(str.compare("banana"), 0); +} + +TEST(SmallStringBase, CompareSmallStringBase) +{ + SmallString str("banana"); + SmallString less("apple"); + SmallString greater("cherry"); + SmallString equal("banana"); + EXPECT_GT(str.compare(less), 0); + EXPECT_LT(str.compare(greater), 0); + EXPECT_EQ(str.compare(equal), 0); +} + +TEST(SmallStringBase, CompareStringView) +{ + SmallString str("banana"); + EXPECT_LT(str.compare("cherry"sv), 0); + EXPECT_GT(str.compare("apple"sv), 0); + EXPECT_EQ(str.compare("banana"sv), 0); +} + +TEST(SmallStringBase, CompareStdString) +{ + SmallString str("banana"); + std::string less = "apple"; + std::string greater = "cherry"; + std::string equal = "banana"; + EXPECT_GT(str.compare(less), 0); + EXPECT_LT(str.compare(greater), 0); + EXPECT_EQ(str.compare(equal), 0); +} + +TEST(SmallStringBase, CompareEmptyStrings) +{ + SmallString str; + EXPECT_EQ(str.compare(""), 0); + EXPECT_LT(str.compare("a"), 0); + + str.assign("a"); + SmallString empty; + EXPECT_GT(str.compare(empty), 0); +} + +TEST(SmallStringBase, CompareDifferentLengths) +{ + SmallString str("abc"); + EXPECT_LT(str.compare("abcd"), 0); + EXPECT_GT(str.compare("ab"), 0); +} + +// Comparison Tests - icompare (case insensitive) +TEST(SmallStringBase, ICompareConstChar) +{ + SmallString str("Banana"); + EXPECT_LT(str.icompare("CHERRY"), 0); + EXPECT_GT(str.icompare("APPLE"), 0); + EXPECT_EQ(str.icompare("BANANA"), 0); +} + +TEST(SmallStringBase, ICompareSmallStringBase) +{ + SmallString str("Banana"); + SmallString equal("BANANA"); + EXPECT_EQ(str.icompare(equal), 0); +} + +TEST(SmallStringBase, ICompareStringView) +{ + SmallString str("Banana"); + EXPECT_EQ(str.icompare("BANANA"sv), 0); +} + +TEST(SmallStringBase, ICompareStdString) +{ + SmallString str("Banana"); + std::string equal = "BANANA"; + EXPECT_EQ(str.icompare(equal), 0); +} + +// StartsWith Tests +TEST(SmallStringBase, StartsWithConstChar) +{ + SmallString str("Hello World"); + EXPECT_TRUE(str.starts_with("Hello")); + EXPECT_TRUE(str.starts_with("H")); + EXPECT_TRUE(str.starts_with("")); + EXPECT_FALSE(str.starts_with("World")); + EXPECT_FALSE(str.starts_with("hello")); +} + +TEST(SmallStringBase, StartsWithCaseInsensitive) +{ + SmallString str("Hello World"); + EXPECT_TRUE(str.starts_with("HELLO", false)); + EXPECT_TRUE(str.starts_with("hello", false)); + EXPECT_FALSE(str.starts_with("hello", true)); +} + +TEST(SmallStringBase, StartsWithSmallStringBase) +{ + SmallString str("Hello World"); + SmallString prefix("Hello"); + EXPECT_TRUE(str.starts_with(prefix)); +} + +TEST(SmallStringBase, StartsWithStringView) +{ + SmallString str("Hello World"); + EXPECT_TRUE(str.starts_with("Hello"sv)); +} + +TEST(SmallStringBase, StartsWithStdString) +{ + SmallString str("Hello World"); + std::string prefix = "Hello"; + EXPECT_TRUE(str.starts_with(prefix)); +} + +TEST(SmallStringBase, StartsWithLongerPrefix) +{ + SmallString str("Hi"); + EXPECT_FALSE(str.starts_with("Hello")); +} + +// EndsWith Tests +TEST(SmallStringBase, EndsWithConstChar) +{ + SmallString str("Hello World"); + EXPECT_TRUE(str.ends_with("World")); + EXPECT_TRUE(str.ends_with("d")); + EXPECT_TRUE(str.ends_with("")); + EXPECT_FALSE(str.ends_with("Hello")); + EXPECT_FALSE(str.ends_with("world")); +} + +TEST(SmallStringBase, EndsWithCaseInsensitive) +{ + SmallString str("Hello World"); + EXPECT_TRUE(str.ends_with("WORLD", false)); + EXPECT_TRUE(str.ends_with("world", false)); + EXPECT_FALSE(str.ends_with("world", true)); +} + +TEST(SmallStringBase, EndsWithSmallStringBase) +{ + SmallString str("Hello World"); + SmallString suffix("World"); + EXPECT_TRUE(str.ends_with(suffix)); +} + +TEST(SmallStringBase, EndsWithStringView) +{ + SmallString str("Hello World"); + EXPECT_TRUE(str.ends_with("World"sv)); +} + +TEST(SmallStringBase, EndsWithStdString) +{ + SmallString str("Hello World"); + std::string suffix = "World"; + EXPECT_TRUE(str.ends_with(suffix)); +} + +TEST(SmallStringBase, EndsWithLongerSuffix) +{ + SmallString str("Hi"); + EXPECT_FALSE(str.ends_with("Hello")); +} + +// Find Tests +TEST(SmallStringBase, FindChar) +{ + SmallString str("Hello World"); + EXPECT_EQ(str.find('o'), 4); + EXPECT_EQ(str.find('W'), 6); + EXPECT_EQ(str.find('z'), -1); +} + +TEST(SmallStringBase, FindCharWithOffset) +{ + SmallString str("Hello World"); + EXPECT_EQ(str.find('o', 5), 7); + EXPECT_EQ(str.find('o', 8), -1); +} + +TEST(SmallStringBase, RFindChar) +{ + SmallString str("Hello World"); + EXPECT_EQ(str.rfind('o'), 7); + EXPECT_EQ(str.rfind('l'), 9); +} + +TEST(SmallStringBase, FindCharEmptyString) +{ + SmallString str; + EXPECT_EQ(str.find('a'), -1); + EXPECT_EQ(str.rfind('a'), -1); +} + +TEST(SmallStringBase, FindString) +{ + SmallString str("Hello World Hello"); + EXPECT_EQ(str.find("Hello"), 0); + EXPECT_EQ(str.find("World"), 6); + EXPECT_EQ(str.find("Goodbye"), -1); +} + +TEST(SmallStringBase, FindStringWithOffset) +{ + SmallString str("Hello World Hello"); + EXPECT_EQ(str.find("Hello", 1), 12); + EXPECT_EQ(str.find("Hello", 13), -1); +} + +// Count Tests +TEST(SmallStringBase, Count) +{ + SmallString str("Hello World"); + EXPECT_EQ(str.count('l'), 3u); + EXPECT_EQ(str.count('o'), 2u); + EXPECT_EQ(str.count('z'), 0u); +} + +// Replace Tests +TEST(SmallStringBase, Replace) +{ + SmallString str("Hello World"); + u32 count = str.replace("World", "Universe"); + EXPECT_EQ(count, 1u); + EXPECT_STREQ(str.c_str(), "Hello Universe"); +} + +TEST(SmallStringBase, ReplaceMultiple) +{ + SmallString str("Hello Hello Hello"); + u32 count = str.replace("Hello", "Hi"); + EXPECT_EQ(count, 3u); + EXPECT_STREQ(str.c_str(), "Hi Hi Hi"); +} + +TEST(SmallStringBase, ReplaceNoMatch) +{ + SmallString str("Hello World"); + u32 count = str.replace("Goodbye", "Hi"); + EXPECT_EQ(count, 0u); + EXPECT_STREQ(str.c_str(), "Hello World"); +} + +TEST(SmallStringBase, ReplaceWithLonger) +{ + SmallString str("Hi"); + str.replace("Hi", "Hello World"); + EXPECT_STREQ(str.c_str(), "Hello World"); +} + +TEST(SmallStringBase, ReplaceWithShorter) +{ + SmallString str("Hello World"); + str.replace("Hello World", "Hi"); + EXPECT_STREQ(str.c_str(), "Hi"); +} + +// Erase Tests +TEST(SmallStringBase, EraseFromOffset) +{ + SmallString str("Hello World"); + str.erase(5); + EXPECT_STREQ(str.c_str(), "Hello"); +} + +TEST(SmallStringBase, EraseWithCount) +{ + SmallString str("Hello World"); + str.erase(5, 1); + EXPECT_STREQ(str.c_str(), "HelloWorld"); +} + +TEST(SmallStringBase, EraseNegativeOffset) +{ + SmallString str("Hello World"); + str.erase(-5); + EXPECT_STREQ(str.c_str(), "Hello "); +} + +TEST(SmallStringBase, EraseAll) +{ + SmallString str("Hello World"); + str.erase(0); + EXPECT_TRUE(str.empty()); +} + +// Reserve/Resize Tests +TEST(SmallStringBase, Reserve) +{ + SmallString str; + str.reserve(100); + EXPECT_GE(str.buffer_size(), 100u); + EXPECT_TRUE(str.empty()); +} + +TEST(SmallStringBase, ResizeGrow) +{ + SmallString str("Hello"); + str.resize(10, 'X'); + EXPECT_EQ(str.length(), 10u); + EXPECT_STREQ(str.c_str(), "HelloXXXXX"); +} + +TEST(SmallStringBase, ResizeShrink) +{ + SmallString str("Hello World"); + str.resize(5); + EXPECT_EQ(str.length(), 5u); + EXPECT_STREQ(str.c_str(), "Hello"); +} + +TEST(SmallStringBase, SetSize) +{ + SmallString str("Hello World"); + str.set_size(5); + EXPECT_EQ(str.length(), 5u); + EXPECT_STREQ(str.c_str(), "Hello"); +} + +TEST(SmallStringBase, UpdateSize) +{ + SmallString str; + str.reserve(20); + std::strcpy(str.data(), "Manual"); + str.update_size(); + EXPECT_EQ(str.length(), 6u); + EXPECT_STREQ(str.c_str(), "Manual"); +} + +TEST(SmallStringBase, MakeRoomFor) +{ + SmallString str("Hello"); + str.make_room_for(1000); + EXPECT_GE(str.buffer_size(), str.length() + 1000u); +} + +// Case Conversion Tests +TEST(SmallStringBase, ConvertToLowerCase) +{ + SmallString str("HELLO WORLD"); + str.convert_to_lower_case(); + EXPECT_STREQ(str.c_str(), "hello world"); +} + +TEST(SmallStringBase, ConvertToUpperCase) +{ + SmallString str("hello world"); + str.convert_to_upper_case(); + EXPECT_STREQ(str.c_str(), "HELLO WORLD"); +} + +// View/Substr Tests +TEST(SmallStringBase, View) +{ + SmallString str("Hello World"); + std::string_view sv = str.view(); + EXPECT_EQ(sv, "Hello World"); +} + +TEST(SmallStringBase, ViewEmpty) +{ + SmallString str; + std::string_view sv = str.view(); + EXPECT_TRUE(sv.empty()); +} + +TEST(SmallStringBase, Substr) +{ + SmallString str("Hello World"); + std::string_view sv = str.substr(0, 5); + EXPECT_EQ(sv, "Hello"); +} + +TEST(SmallStringBase, SubstrNegativeOffset) +{ + SmallString str("Hello World"); + std::string_view sv = str.substr(-5, 5); + EXPECT_EQ(sv, "World"); +} + +TEST(SmallStringBase, SubstrNegativeCount) +{ + SmallString str("Hello World"); + std::string_view sv = str.substr(0, -6); + EXPECT_EQ(sv, "Hello"); +} + +// Span Tests +TEST(SmallStringBase, CSpan) +{ + SmallString str("Hello"); + std::span sp = str.cspan(); + EXPECT_EQ(sp.size(), 5u); + EXPECT_EQ(sp[0], 'H'); +} + +TEST(SmallStringBase, Span) +{ + SmallString str("Hello"); + std::span sp = str.span(); + sp[0] = 'J'; + EXPECT_STREQ(str.c_str(), "Jello"); +} + +TEST(SmallStringBase, CBSpan) +{ + SmallString str("AB"); + std::span sp = str.cbspan(); + EXPECT_EQ(sp.size(), 2u); + EXPECT_EQ(sp[0], static_cast('A')); +} + +TEST(SmallStringBase, BSpan) +{ + SmallString str("AB"); + std::span sp = str.bspan(); + sp[0] = static_cast('X'); + EXPECT_STREQ(str.c_str(), "XB"); +} + +// STL Adapter Tests +TEST(SmallStringBase, Front) +{ + SmallString str("Hello"); + EXPECT_EQ(str.front(), 'H'); +} + +TEST(SmallStringBase, Back) +{ + SmallString str("Hello"); + EXPECT_EQ(str.back(), 'o'); +} + +TEST(SmallStringBase, PushBack) +{ + SmallString str("Hello"); + str.push_back('!'); + EXPECT_STREQ(str.c_str(), "Hello!"); +} + +TEST(SmallStringBase, PopBack) +{ + SmallString str("Hello!"); + str.pop_back(); + EXPECT_STREQ(str.c_str(), "Hello"); +} + +// Accessor Tests +TEST(SmallStringBase, Length) +{ + SmallString str("Hello"); + EXPECT_EQ(str.length(), 5u); +} + +TEST(SmallStringBase, Empty) +{ + SmallString str; + EXPECT_TRUE(str.empty()); + str.assign("x"); + EXPECT_FALSE(str.empty()); +} + +TEST(SmallStringBase, BufferSize) +{ + SmallString str; + EXPECT_GT(str.buffer_size(), 0u); +} + +TEST(SmallStringBase, CStr) +{ + SmallString str("Hello"); + EXPECT_STREQ(str.c_str(), "Hello"); +} + +TEST(SmallStringBase, Data) +{ + SmallString str("Hello"); + str.data()[0] = 'J'; + EXPECT_STREQ(str.c_str(), "Jello"); +} + +TEST(SmallStringBase, EndPtr) +{ + SmallString str("Hello"); + const char* end = str.end_ptr(); + EXPECT_EQ(end - str.c_str(), 5); +} + +// Operator Tests +TEST(SmallStringBase, OperatorConstCharStar) +{ + SmallString str("Hello"); + const char* ptr = str; + EXPECT_STREQ(ptr, "Hello"); +} + +TEST(SmallStringBase, OperatorCharStar) +{ + SmallString str("Hello"); + char* ptr = str; + ptr[0] = 'J'; + EXPECT_STREQ(str.c_str(), "Jello"); +} + +TEST(SmallStringBase, OperatorStringView) +{ + SmallString str("Hello"); + std::string_view sv = str; + EXPECT_EQ(sv, "Hello"); +} + +TEST(SmallStringBase, OperatorEqualityConstChar) +{ + SmallString str("Hello"); + EXPECT_TRUE(str == "Hello"); + EXPECT_FALSE(str == "World"); +} + +TEST(SmallStringBase, OperatorEqualitySmallStringBase) +{ + SmallString str("Hello"); + SmallString other("Hello"); + SmallString different("World"); + EXPECT_TRUE(str == other); + EXPECT_FALSE(str == different); +} + +TEST(SmallStringBase, OperatorEqualityStringView) +{ + SmallString str("Hello"); + EXPECT_TRUE(str == "Hello"sv); + EXPECT_FALSE(str == "World"sv); +} + +TEST(SmallStringBase, OperatorEqualityStdString) +{ + SmallString str("Hello"); + std::string same = "Hello"; + std::string different = "World"; + EXPECT_TRUE(str == same); + EXPECT_FALSE(str == different); +} + +TEST(SmallStringBase, OperatorInequality) +{ + SmallString str("Hello"); + EXPECT_TRUE(str != "World"); + EXPECT_FALSE(str != "Hello"); +} + +TEST(SmallStringBase, OperatorLessThan) +{ + SmallString str("apple"); + EXPECT_TRUE(str < "banana"); + EXPECT_FALSE(str < "aardvark"); +} + +TEST(SmallStringBase, OperatorGreaterThan) +{ + SmallString str("banana"); + EXPECT_TRUE(str > "apple"); + EXPECT_FALSE(str > "cherry"); +} + +TEST(SmallStringBase, OperatorAssignConstChar) +{ + SmallString str; + str = "Hello"; + EXPECT_STREQ(str.c_str(), "Hello"); +} + +TEST(SmallStringBase, OperatorAssignStdString) +{ + SmallString str; + std::string s = "Hello"; + str = s; + EXPECT_STREQ(str.c_str(), "Hello"); +} + +TEST(SmallStringBase, OperatorAssignStringView) +{ + SmallString str; + str = "Hello"sv; + EXPECT_STREQ(str.c_str(), "Hello"); +} + +TEST(SmallStringBase, OperatorAssignSmallStringBase) +{ + SmallString str; + SmallString other("Hello"); + str = other; + EXPECT_STREQ(str.c_str(), "Hello"); +} + +TEST(SmallStringBase, OperatorAssignMove) +{ + SmallString str; + SmallString other("Hello"); + str = std::move(other); + EXPECT_STREQ(str.c_str(), "Hello"); +} + +// ShrinkToFit Tests +TEST(SmallStringBase, ShrinkToFit) +{ + SmallString str; + str.reserve(1000); + str.assign("Hi"); + u32 size_before = str.buffer_size(); + str.shrink_to_fit(); + EXPECT_LE(str.buffer_size(), size_before); + EXPECT_STREQ(str.c_str(), "Hi"); +} + +TEST(SmallStringBase, ShrinkToFitEmpty) +{ + SmallString str; + str.reserve(1000); + str.clear(); + str.shrink_to_fit(); + EXPECT_TRUE(str.empty()); +} + +// Edge Cases +TEST(SmallStringBase, AppendEmptyString) +{ + SmallString str("Hello"); + str.append(""); + EXPECT_STREQ(str.c_str(), "Hello"); +} + +TEST(SmallStringBase, PrependEmptyString) +{ + SmallString str("Hello"); + str.prepend(""); + EXPECT_STREQ(str.c_str(), "Hello"); +} + +TEST(SmallStringBase, InsertEmptyString) +{ + SmallString str("Hello"); + str.insert(2, ""); + EXPECT_STREQ(str.c_str(), "Hello"); +} + +TEST(SmallStringBase, LargeStringAppend) +{ + SmallString str; + for (int i = 0; i < 1000; i++) + { + str.append('X'); + } + EXPECT_EQ(str.length(), 1000u); +} + +TEST(SmallStringBase, EraseNegativeOffsetMiddle) +{ + SmallString str("Hello World"); + // Erase 3 characters starting from position -6 (i.e., position 5 = ' ') + // Should result in "Hellorld" (erase " Wo") + str.erase(-6, 3); + EXPECT_STREQ(str.c_str(), "Hellorld"); + EXPECT_EQ(str.length(), 8u); +} + +TEST(SmallStringBase, MoveAssignmentActuallyMoves) +{ + // Create a string large enough to force heap allocation + SmallString source; + source.reserve(1000); + source.assign("This is a long string that should be on the heap"); + + const char* original_buffer = source.c_str(); + + SmallString dest; + dest = std::move(source); + + EXPECT_STREQ(dest.c_str(), "This is a long string that should be on the heap"); + EXPECT_TRUE(source.empty() || source.c_str() != original_buffer); +} + +TEST(SmallStringBase, SmallStringBaseCaseInsensitive) +{ + SmallString str("Hello"); + SmallString other("HELLO"); + SmallString otherLower("hello"); + SmallString otherMixed("hElLo"); + + EXPECT_TRUE(str.iequals(other)); + EXPECT_TRUE(str.iequals(otherLower)); + EXPECT_TRUE(str.iequals(otherMixed)); + + // Check const char* overloads + EXPECT_TRUE(str.iequals("Hello")); + EXPECT_TRUE(str.iequals("HELLO")); + EXPECT_TRUE(str.iequals("hello")); + EXPECT_TRUE(str.iequals("hElLo")); +} + +#ifdef _WIN32 + +TEST(SmallStringBase, WStringViewAssignNullTermination) +{ + SmallString str; + // First assign something to ensure the buffer has garbage after + str.assign("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"); + + // Now assign a shorter wstring + std::wstring_view wstr = L"Hello"; + str.assign(wstr); + + EXPECT_EQ(str.length(), 5u); + EXPECT_STREQ(str.c_str(), "Hello"); + + // Verify the string is properly null-terminated by checking length matches strlen + EXPECT_EQ(std::strlen(str.c_str()), str.length()); +} + +TEST(SmallStringBase, WStringViewAssignEmptyFollowedByContent) +{ + SmallString str; + str.reserve(100); + + // Assign via wstring_view + std::wstring_view wstr = L"Test"; + str.assign(wstr); + + // The string should be exactly "Test" with no trailing garbage + EXPECT_EQ(str.length(), 4u); + EXPECT_EQ(str.view(), "Test"); + + // Append should work correctly if null-terminated properly + str.append("123"); + EXPECT_STREQ(str.c_str(), "Test123"); +} +#endif + +TEST(SmallStringBase, EraseNegativeOffsetPreservesRemainder) +{ + SmallString str("ABCDEFGHIJ"); + // Erase 2 characters starting from -5 (position 5 = 'F') + // Should result in "ABCDEHIJ" (erase "FG") + str.erase(-5, 2); + EXPECT_STREQ(str.c_str(), "ABCDEHIJ"); + EXPECT_EQ(str.length(), 8u); +} + +TEST(SmallStringBase, AppendHexNullTermination) +{ + SmallString str; + // Pre-fill with garbage to detect missing null terminator + str.assign("XXXXXXXXXXXXXXXXXXXX"); + str.clear(); + + const u8 data[] = {0xAB, 0xCD}; + str.append_hex(data, sizeof(data)); + + EXPECT_EQ(str.length(), 4u); + EXPECT_STREQ(str.c_str(), "abcd"); + // Verify null termination by checking strlen matches length + EXPECT_EQ(std::strlen(str.c_str()), str.length()); +} + +TEST(SmallStringBase, AppendHexCommaSeparatedNullTermination) +{ + SmallString str; + str.assign("XXXXXXXXXXXXXXXXXXXX"); + str.clear(); + + const u8 data[] = {0xAB, 0xCD}; + str.append_hex(data, sizeof(data), true); + + EXPECT_EQ(str.length(), 10u); + EXPECT_STREQ(str.c_str(), "0xab, 0xcd"); + EXPECT_EQ(std::strlen(str.c_str()), str.length()); +} + +TEST(SmallStringBase, AppendHexThenAppendMore) +{ + SmallString str("Prefix: "); + const u8 data[] = {0xFF}; + str.append_hex(data, sizeof(data)); + str.append(" Suffix"); + + // If null terminator is missing, append will start from wrong position + EXPECT_STREQ(str.c_str(), "Prefix: ff Suffix"); +} + +TEST(SmallStringBase, PrependSprintfLargeString) +{ + SmallString str(" end"); + + std::string largeFormat(1500, 'X'); + str.prepend_sprintf("%s", largeFormat.c_str()); + + std::string expected = largeFormat + " end"; + EXPECT_STREQ(str.c_str(), expected.c_str()); +} + +TEST(SmallStringBase, ShrinkToFitEmptyHeapStringResetsState) +{ + SmallString str; + str.reserve(1000); // Force heap allocation + str.clear(); // Empty the string + str.shrink_to_fit(); // Should free heap and restore to valid state + + // After shrink_to_fit on empty heap string, string should still be usable + EXPECT_TRUE(str.empty()); + EXPECT_EQ(str.length(), 0u); + + // These operations should not crash + str.append("Hello"); + EXPECT_STREQ(str.c_str(), "Hello"); +} + +TEST(SmallStringBase, ShrinkToFitEmptyThenClear) +{ + SmallString str; + str.reserve(1000); // Force heap allocation + str.clear(); + str.shrink_to_fit(); + + // clear() should not crash after shrink_to_fit on empty heap string + str.clear(); + EXPECT_TRUE(str.empty()); +} + +TEST(SmallStringBase, ShrinkToFitEmptyThenReserve) +{ + SmallString str; + str.reserve(1000); // Force heap allocation + str.clear(); + str.shrink_to_fit(); + + // reserve() should work correctly after shrink_to_fit on empty heap string + str.reserve(100); + EXPECT_GE(str.buffer_size(), 100u); + + str.assign("Test"); + EXPECT_STREQ(str.c_str(), "Test"); +} + +TEST(SmallStringBase, ReplaceMiddleNullTermination) +{ + SmallString str("Hello World End"); + str.replace("World", "X"); + + EXPECT_EQ(str.length(), 11u); // "Hello X End" = 11 chars + EXPECT_STREQ(str.c_str(), "Hello X End"); + + // Verify null termination by checking strlen matches length + EXPECT_EQ(std::strlen(str.c_str()), str.length()); +} + +TEST(SmallStringBase, ReplaceMiddleShorterReplacement) +{ + SmallString str("AAABBBCCC"); + str.replace("BBB", "X"); + + EXPECT_EQ(str.length(), 7u); // "AAAXCCC" = 7 chars + EXPECT_STREQ(str.c_str(), "AAAXCCC"); + EXPECT_EQ(std::strlen(str.c_str()), str.length()); +} + +TEST(SmallStringBase, ReplaceMiddleThenAppend) +{ + SmallString str("Hello World End"); + str.replace("World", "X"); + + // If null terminator is missing, append will start from wrong position + str.append("!"); + EXPECT_STREQ(str.c_str(), "Hello X End!"); + EXPECT_EQ(str.length(), 12u); +} + +TEST(SmallStringBase, ReplaceMultipleInMiddle) +{ + SmallString str("aXXXbXXXc"); + u32 count = str.replace("XXX", "Y"); + + EXPECT_EQ(count, 2u); + EXPECT_STREQ(str.c_str(), "aYbYc"); + EXPECT_EQ(str.length(), 5u); + EXPECT_EQ(std::strlen(str.c_str()), str.length()); +} + +TEST(SmallStringBase, ReplaceEmptySearchString) +{ + SmallString str("Hello"); + + // This should either be a no-op or handle gracefully, not infinite loop + // Set a timeout expectation or just verify it completes + u32 count = str.replace("", "X"); + + // Expected: either 0 replacements, or reasonable behavior + // Actual with bug: infinite loop (test hangs) + EXPECT_EQ(count, 0u); + EXPECT_STREQ(str.c_str(), "Hello"); +} + +////////////////////////////////////////////////////////////////////////// +// SmallStackString Test Suite +////////////////////////////////////////////////////////////////////////// + +TEST(SmallStackString, DefaultConstructor) +{ + SmallString s; + EXPECT_TRUE(s.empty()); + EXPECT_EQ(s.length(), 0u); + EXPECT_EQ(s.buffer_size(), 256u); +} + +TEST(SmallStackString, ConstCharConstructor) +{ + SmallString s("Hello"); + EXPECT_EQ(s.length(), 5u); + EXPECT_STREQ(s.c_str(), "Hello"); +} + +TEST(SmallStackString, ConstCharWithLengthConstructor) +{ + SmallString s("Hello World", 5); + EXPECT_EQ(s.length(), 5u); + EXPECT_STREQ(s.c_str(), "Hello"); +} + +TEST(SmallStackString, CopyConstructorFromBase) +{ + SmallString original("Test"); + SmallStringBase& base = original; + SmallString copy(base); + EXPECT_STREQ(copy.c_str(), "Test"); +} + +TEST(SmallStackString, MoveConstructorFromBase) +{ + SmallString original("Test"); + SmallStringBase& base = original; + SmallString moved(std::move(base)); + EXPECT_STREQ(moved.c_str(), "Test"); +} + +TEST(SmallStackString, CopyConstructorFromSame) +{ + SmallString original("Test"); + SmallString copy(original); + EXPECT_STREQ(copy.c_str(), "Test"); +} + +TEST(SmallStackString, MoveConstructorFromSame) +{ + SmallString original("Test"); + SmallString moved(std::move(original)); + EXPECT_STREQ(moved.c_str(), "Test"); +} + +TEST(SmallStackString, StdStringConstructor) +{ + std::string s = "Hello"; + SmallString ss(s); + EXPECT_STREQ(ss.c_str(), "Hello"); +} + +TEST(SmallStackString, StringViewConstructor) +{ + SmallString ss("Hello"sv); + EXPECT_STREQ(ss.c_str(), "Hello"); +} + +TEST(SmallStackString, AssignmentFromBase) +{ + SmallString str; + SmallString other("Source"); + SmallStringBase& base = other; + str = base; + EXPECT_STREQ(str.c_str(), "Source"); +} + +TEST(SmallStackString, AssignmentMoveFromBase) +{ + SmallString str; + SmallString other("Source"); + SmallStringBase& base = other; + str = std::move(base); + EXPECT_STREQ(str.c_str(), "Source"); +} + +TEST(SmallStackString, AssignmentFromSame) +{ + SmallString str; + SmallString other("Source"); + str = other; + EXPECT_STREQ(str.c_str(), "Source"); +} + +TEST(SmallStackString, AssignmentMoveFromSame) +{ + SmallString str; + SmallString other("Source"); + str = std::move(other); + EXPECT_STREQ(str.c_str(), "Source"); +} + +TEST(SmallStackString, AssignmentFromStdString) +{ + SmallString str; + std::string s = "Source"; + str = s; + EXPECT_STREQ(str.c_str(), "Source"); +} + +TEST(SmallStackString, AssignmentFromStringView) +{ + SmallString str; + str = "Source"sv; + EXPECT_STREQ(str.c_str(), "Source"); +} + +TEST(SmallStackString, AssignmentFromConstChar) +{ + SmallString str; + str = "Source"; + EXPECT_STREQ(str.c_str(), "Source"); +} + +TEST(SmallStackString, FromSprintf) +{ + SmallString s = SmallString::from_sprintf("Value: %d", 42); + EXPECT_STREQ(s.c_str(), "Value: 42"); +} + +TEST(SmallStackString, FromFormat) +{ + SmallString s = SmallString::from_format("Value: {}", 42); + EXPECT_STREQ(s.c_str(), "Value: 42"); +} + +TEST(SmallStackString, FromVFormat) +{ + constexpr int i = 42; + auto args = fmt::make_format_args(i); + SmallString s = SmallString::from_vformat("Value: {}", args); + EXPECT_STREQ(s.c_str(), "Value: 42"); +} + +TEST(SmallStackString, TinyStringSize) +{ + TinyString tiny; + EXPECT_EQ(tiny.buffer_size(), 64u); +} + +TEST(SmallStackString, SmallStringSize) +{ + SmallString small; + EXPECT_EQ(small.buffer_size(), 256u); +} + +TEST(SmallStackString, LargeStringSize) +{ + LargeString large; + EXPECT_EQ(large.buffer_size(), 512u); +} + +TEST(SmallStackString, StackBufferOverflow) +{ + TinyString tiny; + // TinyString has 64 bytes, so appending more should trigger heap allocation + for (int i = 0; i < 100; i++) + { + tiny.append('X'); + } + EXPECT_EQ(tiny.length(), 100u); + EXPECT_GE(tiny.buffer_size(), 100u); +} + +TEST(SmallStackString, FmtFormatter) +{ + SmallString s("test"); + std::string formatted = fmt::format("Value: {}", s); + EXPECT_EQ(formatted, "Value: test"); +} + +TEST(SmallStackString, TinyStringFmtFormatter) +{ + TinyString s("tiny"); + std::string formatted = fmt::format("Value: {}", s); + EXPECT_EQ(formatted, "Value: tiny"); +} + +TEST(SmallStackString, LargeStringFmtFormatter) +{ + LargeString s("large"); + std::string formatted = fmt::format("Value: {}", s); + EXPECT_EQ(formatted, "Value: large"); +} + + +TEST(SmallStackString, MoveConstructorFromSameType) +{ + SmallString source; + source.reserve(1000); // Force heap allocation + source.assign("Heap allocated string"); + + const char* original_buffer = source.c_str(); + + SmallString dest(std::move(source)); + + EXPECT_STREQ(dest.c_str(), "Heap allocated string"); + // After proper move, source should be empty or have different buffer + EXPECT_TRUE(source.empty() || source.c_str() != original_buffer); +} + +TEST(SmallStackString, MoveConstructorFromBaseHeap) +{ + SmallString source; + source.reserve(1000); // Force heap allocation + source.assign("Heap allocated string"); + + const char* original_buffer = source.c_str(); + SmallStringBase& baseRef = source; + + SmallString dest(std::move(baseRef)); + + EXPECT_STREQ(dest.c_str(), "Heap allocated string"); + // After proper move, source should be empty or have different buffer + EXPECT_TRUE(source.empty() || source.c_str() != original_buffer); +} diff --git a/src/common/heap_array.h b/src/common/heap_array.h index a5279088d..5313e6f34 100644 --- a/src/common/heap_array.h +++ b/src/common/heap_array.h @@ -13,6 +13,9 @@ template class FixedHeapArray { + static_assert(std::is_trivially_copyable_v, "T is trivially copyable"); + static_assert(std::is_standard_layout_v, "T is standard layout"); + public: using value_type = T; using size_type = std::size_t; @@ -73,12 +76,12 @@ public: void swap(this_type& move) { std::swap(m_data, move.m_data); } - std::span span() { return std::span(m_data); } - std::span cspan() const { return std::span(m_data); } + std::span span() { return std::span(m_data, m_data + SIZE); } + std::span cspan() const { return std::span(m_data, m_data + SIZE); } this_type& operator=(const this_type& rhs) { - std::copy(begin(), end(), rhs.cbegin()); + std::copy(rhs.cbegin(), rhs.cend(), begin()); return *this; } @@ -90,24 +93,12 @@ public: return *this; } -#define RELATIONAL_OPERATOR(op) \ - bool operator op(const this_type& rhs) const \ - { \ - for (size_type i = 0; i < SIZE; i++) \ - { \ - if (!(m_data[i] op rhs.m_data[i])) \ - return false; \ - } \ - } - - RELATIONAL_OPERATOR(==); - RELATIONAL_OPERATOR(!=); - RELATIONAL_OPERATOR(<); - RELATIONAL_OPERATOR(<=); - RELATIONAL_OPERATOR(>); - RELATIONAL_OPERATOR(>=); - -#undef RELATIONAL_OPERATOR + bool operator==(const this_type& rhs) const { return (std::memcmp(m_data, rhs.m_data, SIZE * sizeof(T)) == 0); } + bool operator!=(const this_type& rhs) const { return (std::memcmp(m_data, rhs.m_data, SIZE * sizeof(T)) != 0); } + bool operator<(const this_type& rhs) const { return (std::memcmp(m_data, rhs.m_data, SIZE * sizeof(T)) < 0); } + bool operator<=(const this_type& rhs) const { return (std::memcmp(m_data, rhs.m_data, SIZE * sizeof(T)) <= 0); } + bool operator>(const this_type& rhs) const { return (std::memcmp(m_data, rhs.m_data, SIZE * sizeof(T)) < 0); } + bool operator>=(const this_type& rhs) const { return (std::memcmp(m_data, rhs.m_data, SIZE * sizeof(T)) >= 0); } private: void allocate() @@ -372,26 +363,87 @@ public: return *this; } -#define RELATIONAL_OPERATOR(op, size_op) \ - bool operator op(const this_type& rhs) const \ - { \ - if (m_size != rhs.m_size) \ - return m_size size_op rhs.m_size; \ - for (size_type i = 0; i < m_size; i++) \ - { \ - if (!(m_data[i] op rhs.m_data[i])) \ - return false; \ - } \ + bool operator==(const this_type& rhs) const + { + if (m_size != rhs.m_size) + return false; + + if (m_size == 0) + return true; + + return (std::memcmp(m_data, rhs.m_data, m_size * sizeof(T)) == 0); } - RELATIONAL_OPERATOR(==, !=); - RELATIONAL_OPERATOR(!=, ==); - RELATIONAL_OPERATOR(<, <); - RELATIONAL_OPERATOR(<=, <=); - RELATIONAL_OPERATOR(>, >); - RELATIONAL_OPERATOR(>=, >=); + bool operator!=(const this_type& rhs) const + { + if (m_size != rhs.m_size) + return true; -#undef RELATIONAL_OPERATOR + if (m_size == 0) + return false; + + return (std::memcmp(m_data, rhs.m_data, m_size * sizeof(T)) != 0); + } + + bool operator<(const this_type& rhs) const + { + const size_type min_size = std::min(m_size, rhs.m_size); + for (size_type i = 0; i < min_size; i++) + { + if (!(m_data[i] < rhs.m_data[i])) + return false; + } + + if (m_size != rhs.m_size) + return m_size < rhs.m_size; + + return true; + } + + bool operator<=(const this_type& rhs) const + { + const size_type min_size = std::min(m_size, rhs.m_size); + for (size_type i = 0; i < min_size; i++) + { + if (!(m_data[i] <= rhs.m_data[i])) + return false; + } + + if (m_size != rhs.m_size) + return m_size <= rhs.m_size; + + return true; + } + + bool operator>(const this_type& rhs) const + { + const size_type min_size = std::min(m_size, rhs.m_size); + for (size_type i = 0; i < min_size; i++) + { + if (!(m_data[i] > rhs.m_data[i])) + return false; + } + + if (m_size != rhs.m_size) + return m_size > rhs.m_size; + + return true; + } + + bool operator>=(const this_type& rhs) const + { + const size_type min_size = std::min(m_size, rhs.m_size); + for (size_type i = 0; i < min_size; i++) + { + if (!(m_data[i] >= rhs.m_data[i])) + return false; + } + + if (m_size != rhs.m_size) + return m_size >= rhs.m_size; + + return true; + } private: void internal_resize(size_t size, T* prev_ptr, [[maybe_unused]] size_t prev_size) diff --git a/src/common/small_string.cpp b/src/common/small_string.cpp index 1284b7e29..e4a330e9d 100644 --- a/src/common/small_string.cpp +++ b/src/common/small_string.cpp @@ -86,7 +86,7 @@ void SmallStringBase::reserve(u32 new_reserve) m_on_heap = true; } - m_buffer_size = new_reserve; + m_buffer_size = real_reserve; } void SmallStringBase::shrink_to_fit() @@ -100,6 +100,7 @@ void SmallStringBase::shrink_to_fit() std::free(m_buffer); m_buffer = nullptr; m_buffer_size = 0; + m_on_heap = false; return; } @@ -130,7 +131,7 @@ std::string_view SmallStringBase::view() const SmallStringBase& SmallStringBase::operator=(SmallStringBase&& move) { - assign(move); + assign(std::move(move)); return *this; } @@ -160,15 +161,16 @@ SmallStringBase& SmallStringBase::operator=(const SmallStringBase& copy) void SmallStringBase::make_room_for(u32 space) { - const u32 required_size = m_length + space + 1; - if (m_buffer_size >= required_size) + const u32 required_length = m_length + space; + if (m_buffer_size > required_length) return; - reserve(std::max(required_size, m_buffer_size * 2)); + reserve(std::max(required_length, m_buffer_size * 2)); } void SmallStringBase::append(const char* str, u32 length) { + DebugAssert(str != m_buffer); // appending self is not allowed if (length == 0) return; @@ -215,6 +217,8 @@ void SmallStringBase::append_hex(const void* data, size_t len, bool comma_separa m_buffer[m_length++] = hex_char(bytes[i] & 0xF); } } + + m_buffer[m_length] = '\0'; } void SmallStringBase::prepend(const char* str, u32 length) @@ -222,6 +226,7 @@ void SmallStringBase::prepend(const char* str, u32 length) if (length == 0) return; + DebugAssert(str != m_buffer); // appending self is not allowed make_room_for(length); DebugAssert((length + m_length) < m_buffer_size); @@ -239,11 +244,13 @@ void SmallStringBase::append(char c) void SmallStringBase::append(const SmallStringBase& str) { + DebugAssert(&str != this); // appending self is not allowed append(str.m_buffer, str.m_length); } void SmallStringBase::append(const char* str) { + DebugAssert(str != m_buffer); // appending self is not allowed append(str, static_cast(std::strlen(str))); } @@ -254,6 +261,7 @@ void SmallStringBase::append(const std::string& str) void SmallStringBase::append(const std::string_view str) { + DebugAssert(str.data() != m_buffer); // appending self is not allowed append(str.data(), static_cast(str.length())); } @@ -307,11 +315,13 @@ void SmallStringBase::prepend(char c) void SmallStringBase::prepend(const SmallStringBase& str) { + DebugAssert(&str != this); // prepending self is not allowed prepend(str.m_buffer, str.m_length); } void SmallStringBase::prepend(const char* str) { + DebugAssert(str != m_buffer); // prepending self is not allowed prepend(str, static_cast(std::strlen(str))); } @@ -322,6 +332,7 @@ void SmallStringBase::prepend(const std::string& str) void SmallStringBase::prepend(const std::string_view str) { + DebugAssert(str.data() != m_buffer); // prepending self is not allowed prepend(str.data(), static_cast(str.length())); } @@ -345,7 +356,10 @@ void SmallStringBase::prepend_vsprintf(const char* format, va_list ArgPtr) for (;;) { - int ret = std::vsnprintf(buffer, buffer_size, format, ArgPtr); + std::va_list ap_copy; + va_copy(ap_copy, ArgPtr); + int ret = std::vsnprintf(buffer, buffer_size, format, ap_copy); + va_end(ap_copy); if (ret < 0 || (static_cast(ret) >= (buffer_size - 1))) { buffer_size *= 2; @@ -367,11 +381,13 @@ void SmallStringBase::prepend_vsprintf(const char* format, va_list ArgPtr) void SmallStringBase::insert(s32 offset, const char* str) { + DebugAssert(str != m_buffer); // inserting self is not allowed insert(offset, str, static_cast(std::strlen(str))); } void SmallStringBase::insert(s32 offset, const SmallStringBase& str) { + DebugAssert(&str != this); // inserting self is not allowed insert(offset, str, str.m_length); } @@ -393,7 +409,7 @@ void SmallStringBase::insert(s32 offset, const char* str, u32 length) DebugAssert(real_offset <= m_length); const u32 chars_after_offset = m_length - real_offset; if (chars_after_offset > 0) - std::memmove(m_buffer + offset + length, m_buffer + offset, chars_after_offset); + std::memmove(m_buffer + real_offset + length, m_buffer + real_offset, chars_after_offset); // insert the string std::memcpy(m_buffer + real_offset, str, length); @@ -410,6 +426,7 @@ void SmallStringBase::insert(s32 offset, const std::string& str) void SmallStringBase::insert(s32 offset, const std::string_view str) { + DebugAssert(str.data() != m_buffer); // inserting self is not allowed insert(offset, str.data(), static_cast(str.size())); } @@ -497,6 +514,7 @@ void SmallStringBase::assign(const std::wstring_view wstr) } m_length = static_cast(mblen); + m_buffer[m_length] = '\0'; } std::wstring SmallStringBase::wstring() const @@ -572,7 +590,8 @@ bool SmallStringBase::iequals(const char* otherText) const bool SmallStringBase::iequals(const SmallStringBase& str) const { - return (m_length == str.m_length && (m_length == 0 || std::strcmp(m_buffer, str.m_buffer) == 0)); + return (m_length == str.m_length && + (m_length == 0 || StringUtil::Strncasecmp(m_buffer, str.m_buffer, m_length) == 0)); } bool SmallStringBase::iequals(const std::string_view str) const @@ -771,6 +790,9 @@ bool SmallStringBase::ends_with(const std::string& str, bool case_sensitive) con void SmallStringBase::clear() { + if (m_buffer_size == 0) + return; + // in debug, zero whole string, in release, zero only the first character #if _DEBUG std::memset(m_buffer, 0, m_buffer_size); @@ -823,6 +845,9 @@ u32 SmallStringBase::count(char ch) const u32 SmallStringBase::replace(const char* search, const char* replacement) { const u32 search_length = static_cast(std::strlen(search)); + if (search_length == 0) + return 0; + const u32 replacement_length = static_cast(std::strlen(replacement)); s32 offset = 0; @@ -833,17 +858,19 @@ u32 SmallStringBase::replace(const char* search, const char* replacement) if (offset < 0) break; + const u32 chars_after_offset = (m_length - static_cast(offset)); + DebugAssert(chars_after_offset >= search_length); + const u32 new_length = m_length - search_length + replacement_length; reserve(new_length); m_length = new_length; - const u32 chars_after_offset = (m_length - static_cast(offset)); - DebugAssert(chars_after_offset >= search_length); if (chars_after_offset > search_length) { std::memmove(&m_buffer[static_cast(offset) + replacement_length], &m_buffer[static_cast(offset) + search_length], chars_after_offset - search_length); std::memcpy(&m_buffer[static_cast(offset)], replacement, replacement_length); + m_buffer[m_length] = '\0'; } else { @@ -861,22 +888,26 @@ u32 SmallStringBase::replace(const char* search, const char* replacement) void SmallStringBase::resize(u32 new_size, char fill, bool shrink_if_smaller) { - // if going larger, or we don't own the buffer, realloc - if (new_size >= m_buffer_size) + if (new_size > m_length) { + // expanding - ensure we have space reserve(new_size); - if (m_length < new_size) - { - std::memset(m_buffer + m_length, fill, m_buffer_size - m_length - 1); - } - + // fill the expanded area with the fill character + std::memset(m_buffer + m_length, fill, new_size - m_length); m_length = new_size; + +#ifdef _DEBUG + // zero remaining unused buffer in debug + std::memset(m_buffer + m_length, 0, m_buffer_size - new_size); +#else + m_buffer[m_length] = 0; +#endif } else { - // update length and terminator -#if _DEBUG + // shrinking or same size - update length and terminator +#ifdef _DEBUG std::memset(m_buffer + new_size, 0, m_buffer_size - new_size); #else m_buffer[new_size] = 0; @@ -975,7 +1006,7 @@ void SmallStringBase::erase(s32 offset, s32 count) const u32 after_erase_block = m_length - real_offset - real_count; DebugAssert(after_erase_block > 0); - std::memmove(m_buffer + offset, m_buffer + real_offset + real_count, after_erase_block); + std::memmove(m_buffer + real_offset, m_buffer + real_offset + real_count, after_erase_block); m_length = m_length - real_count; #ifdef _DEBUG diff --git a/src/common/small_string.h b/src/common/small_string.h index 5e5dbdaed..eb1106329 100644 --- a/src/common/small_string.h +++ b/src/common/small_string.h @@ -184,6 +184,9 @@ public: // returns the end of the string (pointer is past the last character) ALWAYS_INLINE const char* end_ptr() const { return m_buffer + m_length; } + // returns true if the string is heap-allocated + ALWAYS_INLINE bool is_heap_allocated() const { return m_on_heap; } + // STL adapters ALWAYS_INLINE char& front() { return m_buffer[0]; } ALWAYS_INLINE const char& front() const { return m_buffer[0]; } @@ -287,7 +290,7 @@ public: ALWAYS_INLINE SmallStackString(SmallStringBase&& move) { init(); - assign(move); + move_assign(std::move(move)); } ALWAYS_INLINE explicit SmallStackString(const SmallStackString& copy) @@ -299,7 +302,7 @@ public: ALWAYS_INLINE explicit SmallStackString(SmallStackString&& move) { init(); - assign(move); + move_assign(std::move(move)); } ALWAYS_INLINE explicit SmallStackString(const std::string& str) @@ -322,7 +325,7 @@ public: ALWAYS_INLINE SmallStackString& operator=(SmallStringBase&& move) { - assign(move); + move_assign(std::move(move)); return *this; } @@ -334,7 +337,7 @@ public: ALWAYS_INLINE SmallStackString& operator=(SmallStackString&& move) { - assign(move); + move_assign(std::move(move)); return *this; } @@ -378,6 +381,15 @@ private: m_stack_buffer[0] = '\0'; #endif } + + ALWAYS_INLINE void move_assign(SmallStringBase&& move) + { + // only move if on the heap, otherwise copy + if (move.is_heap_allocated()) + SmallStringBase::assign(std::move(move)); + else + assign(move.data(), move.length()); + } }; #ifdef _MSC_VER