Common: Add additional unit tests

And fix several logical errors.
This commit is contained in:
Stenzek
2026-01-31 13:57:51 +10:00
parent d6915fb57a
commit b20c2b281f
11 changed files with 3414 additions and 66 deletions

View File

@@ -1,3 +1,13 @@
# SPDX-FileCopyrightText: 2019-2026 Connor McLaughlin <stenzek@gmail.com>
# 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)

View File

@@ -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.")

View File

@@ -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
)

View File

@@ -0,0 +1,868 @@
// SPDX-FileCopyrightText: 2019-2026 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
#include "common/binary_reader_writer.h"
#include "common/small_string.h"
#include <array>
#include <cstring>
#include <gtest/gtest.h>
#include <optional>
#include <string>
#include <string_view>
using namespace std::string_view_literals;
//////////////////////////////////////////////////////////////////////////
// Global test data
//////////////////////////////////////////////////////////////////////////
// Buffer with various primitive types
alignas(8) static constexpr std::array<u8, 32> 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<u8, 16> 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<u8, 25> 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<s8>(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<s16>(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<s32>(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<u8, 2> 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<s8>(0xFE));
EXPECT_EQ(reader.ReadU16(), 0x1234u);
EXPECT_EQ(reader.ReadS16(), static_cast<s16>(0xABCD));
EXPECT_EQ(reader.ReadU32(), 0x12345678u);
EXPECT_EQ(reader.ReadS32(), static_cast<s32>(0xEDCBA988));
EXPECT_EQ(reader.ReadU64(), 0x0123456789ABCDEFull);
EXPECT_FLOAT_EQ(reader.ReadFloat(), 1.0f);
}
TEST(BinarySpanReader, ReadBoolReturningValue)
{
std::array<u8, 3> 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<u8, 3> 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<u8, 2> 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<s8>(0xFE));
EXPECT_EQ(u16val, 0x1234u);
EXPECT_EQ(s16val, static_cast<s16>(0xABCD));
EXPECT_EQ(u32val, 0x12345678u);
EXPECT_EQ(s32val, static_cast<s32>(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<u8, 5> buf = {0x01, 0x78, 0x56, 0x34, 0x12}; // has_value=true, value=0x12345678
BinarySpanReader reader(buf);
std::optional<u32> val;
EXPECT_TRUE(reader.ReadOptionalT(&val));
EXPECT_TRUE(val.has_value());
EXPECT_EQ(val.value(), 0x12345678u);
}
TEST(BinarySpanReader, ReadOptionalTWithoutValue)
{
std::array<u8, 5> buf = {0x00, 0x00, 0x00, 0x00, 0x00}; // has_value=false
BinarySpanReader reader(buf);
std::optional<u32> 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<u8, 2> buf = {0x01, 0x02};
BinarySpanReader reader(buf);
u32 val;
EXPECT_FALSE(reader.ReadU32(&val));
}
TEST(BinarySpanReader, PeekBeyondBuffer)
{
std::array<u8, 2> 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<u8, 32> buf = {};
BinarySpanWriter writer(buf);
EXPECT_TRUE(writer.IsValid());
EXPECT_EQ(writer.GetBufferRemaining(), buf.size());
EXPECT_EQ(writer.GetBufferWritten(), 0u);
}
TEST(BinarySpanWriter, MoveConstructor)
{
std::array<u8, 32> 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<u8, 32> 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<u8, 32> 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<u8, 4> buf = {};
BinarySpanWriter writer(buf);
EXPECT_TRUE(writer.WriteU8(0xAB));
EXPECT_EQ(buf[0], 0xABu);
EXPECT_EQ(writer.GetBufferWritten(), 1u);
}
TEST(BinarySpanWriter, WriteS8)
{
std::array<u8, 4> buf = {};
BinarySpanWriter writer(buf);
EXPECT_TRUE(writer.WriteS8(-5));
EXPECT_EQ(static_cast<s8>(buf[0]), -5);
}
TEST(BinarySpanWriter, WriteU16)
{
std::array<u8, 4> buf = {};
BinarySpanWriter writer(buf);
EXPECT_TRUE(writer.WriteU16(0x1234));
EXPECT_EQ(buf[0], 0x34u);
EXPECT_EQ(buf[1], 0x12u);
}
TEST(BinarySpanWriter, WriteS16)
{
std::array<u8, 4> 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<u8, 4> 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<u8, 4> 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<u8, 8> buf = {};
BinarySpanWriter writer(buf);
EXPECT_TRUE(writer.WriteU64(0x0123456789ABCDEFull));
EXPECT_EQ(buf[0], 0xEFu);
EXPECT_EQ(buf[7], 0x01u);
}
TEST(BinarySpanWriter, WriteS64)
{
std::array<u8, 8> 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<u8, 4> 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<u8, 2> 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<u8, 16> 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<u8, 16> buf = {};
BinarySpanWriter writer(buf);
EXPECT_TRUE(writer.WriteCString(""));
EXPECT_EQ(buf[0], '\0');
EXPECT_EQ(writer.GetBufferWritten(), 1u);
}
TEST(BinarySpanWriter, WriteSizePrefixedString)
{
std::array<u8, 16> 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<u8, 16> 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<u8, 32> buf = {};
BinarySpanWriter writer(buf);
writer << static_cast<u8>(0x01) << static_cast<s8>(0xFE) << static_cast<u16>(0x1234) << static_cast<s16>(0xABCD)
<< static_cast<u32>(0x12345678) << static_cast<s32>(0xEDCBA988) << static_cast<u64>(0x0123456789ABCDEFull)
<< 1.0f;
EXPECT_EQ(writer.GetBufferWritten(), 26u);
// Verify by reading back
BinarySpanReader reader(std::span<const u8>(buf.data(), writer.GetBufferWritten()));
EXPECT_EQ(reader.ReadU8(), 0x01u);
EXPECT_EQ(reader.ReadS8(), static_cast<s8>(0xFE));
EXPECT_EQ(reader.ReadU16(), 0x1234u);
EXPECT_EQ(reader.ReadS16(), static_cast<s16>(0xABCD));
EXPECT_EQ(reader.ReadU32(), 0x12345678u);
EXPECT_EQ(reader.ReadS32(), static_cast<s32>(0xEDCBA988));
EXPECT_EQ(reader.ReadU64(), 0x0123456789ABCDEFull);
EXPECT_FLOAT_EQ(reader.ReadFloat(), 1.0f);
}
TEST(BinarySpanWriter, StreamOperatorCString)
{
std::array<u8, 16> buf = {};
BinarySpanWriter writer(buf);
writer << "Hello"sv;
BinarySpanReader reader(std::span<const u8>(buf.data(), writer.GetBufferWritten()));
EXPECT_EQ(reader.ReadCString(), "Hello"sv);
}
TEST(BinarySpanWriter, WriteOptionalTWithValue)
{
std::array<u8, 8> buf = {};
BinarySpanWriter writer(buf);
std::optional<u32> val = 0x12345678u;
EXPECT_TRUE(writer.WriteOptionalT(val));
EXPECT_EQ(writer.GetBufferWritten(), 5u);
BinarySpanReader reader(std::span<const u8>(buf.data(), writer.GetBufferWritten()));
EXPECT_TRUE(reader.ReadBool());
EXPECT_EQ(reader.ReadU32(), 0x12345678u);
}
TEST(BinarySpanWriter, WriteOptionalTWithoutValue)
{
std::array<u8, 8> buf = {};
BinarySpanWriter writer(buf);
std::optional<u32> val;
EXPECT_TRUE(writer.WriteOptionalT(val));
EXPECT_EQ(writer.GetBufferWritten(), 1u);
BinarySpanReader reader(std::span<const u8>(buf.data(), writer.GetBufferWritten()));
EXPECT_FALSE(reader.ReadBool());
}
TEST(BinarySpanWriter, GetRemainingSpan)
{
std::array<u8, 32> buf = {};
BinarySpanWriter writer(buf);
writer.IncrementPosition(10);
auto remaining = writer.GetRemainingSpan();
EXPECT_EQ(remaining.size(), buf.size() - 10);
}
TEST(BinarySpanWriter, GetRemainingSpanWithSize)
{
std::array<u8, 32> buf = {};
BinarySpanWriter writer(buf);
writer.IncrementPosition(10);
auto remaining = writer.GetRemainingSpan(5);
EXPECT_EQ(remaining.size(), 5u);
}
TEST(BinarySpanWriter, WriteBeyondBuffer)
{
std::array<u8, 2> buf = {};
BinarySpanWriter writer(buf);
EXPECT_FALSE(writer.WriteU32(0x12345678));
}
TEST(BinarySpanWriter, WriteCStringBeyondBuffer)
{
std::array<u8, 4> buf = {};
BinarySpanWriter writer(buf);
EXPECT_FALSE(writer.WriteCString("Hello")); // needs 6 bytes
}
TEST(BinarySpanWriter, WriteSizePrefixedStringBeyondBuffer)
{
std::array<u8, 4> buf = {};
BinarySpanWriter writer(buf);
EXPECT_FALSE(writer.WriteSizePrefixedString("Test")); // needs 8 bytes
}
//////////////////////////////////////////////////////////////////////////
// Round-trip Tests (Writer -> Reader)
//////////////////////////////////////////////////////////////////////////
TEST(BinarySpanRoundTrip, AllPrimitiveTypes)
{
std::array<u8, 64> 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<const u8>(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<u8, 64> buf = {};
BinarySpanWriter writer(buf);
writer.WriteCString("Hello");
writer.WriteCString("");
writer.WriteCString("World!");
BinarySpanReader reader(std::span<const u8>(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<u8, 64> buf = {};
BinarySpanWriter writer(buf);
writer.WriteSizePrefixedString("Testing");
writer.WriteSizePrefixedString("");
writer.WriteSizePrefixedString("123");
BinarySpanReader reader(std::span<const u8>(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<u8, 32> buf = {};
BinarySpanWriter writer(buf);
std::optional<u32> val1 = 12345u;
std::optional<u32> val2;
std::optional<u16> val3 = u16(9999);
writer.WriteOptionalT(val1);
writer.WriteOptionalT(val2);
writer.WriteOptionalT(val3);
BinarySpanReader reader(std::span<const u8>(buf.data(), writer.GetBufferWritten()));
std::optional<u32> read1, read2;
std::optional<u16> 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<u8, 128> 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<const u8>(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());
}

View File

@@ -3,12 +3,15 @@
<Import Project="..\..\dep\msvc\vsprops\Configurations.props" />
<ItemGroup>
<ClCompile Include="..\..\dep\googletest\src\gtest_main.cc" />
<ClCompile Include="binary_reader_writer_tests.cpp" />
<ClCompile Include="bitutils_tests.cpp" />
<ClCompile Include="file_system_tests.cpp" />
<ClCompile Include="gsvector_tests.cpp" />
<ClCompile Include="heap_array_tests.cpp" />
<ClCompile Include="path_tests.cpp" />
<ClCompile Include="rectangle_tests.cpp" />
<ClCompile Include="hash_tests.cpp" />
<ClCompile Include="small_string_tests.cpp" />
<ClCompile Include="string_tests.cpp" />
<ClCompile Include="gsvector_yuvtorgb_test.cpp" />
</ItemGroup>

View File

@@ -10,5 +10,8 @@
<ClCompile Include="gsvector_yuvtorgb_test.cpp" />
<ClCompile Include="hash_tests.cpp" />
<ClCompile Include="gsvector_tests.cpp" />
<ClCompile Include="small_string_tests.cpp" />
<ClCompile Include="binary_reader_writer_tests.cpp" />
<ClCompile Include="heap_array_tests.cpp" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,827 @@
// SPDX-FileCopyrightText: 2019-2025 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
#include "common/heap_array.h"
#include <gtest/gtest.h>
// ============================================================================
// FixedHeapArray Tests
// ============================================================================
TEST(FixedHeapArray, DefaultConstruction)
{
FixedHeapArray<int, 10> 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<int, 5> arr1;
for (size_t i = 0; i < arr1.size(); ++i)
arr1[i] = static_cast<int>(i * 10);
FixedHeapArray<int, 5> 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<int, 5> arr1;
int* original_data = arr1.data();
for (size_t i = 0; i < arr1.size(); ++i)
arr1[i] = static_cast<int>(i * 10);
FixedHeapArray<int, 5> 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<int>(i * 10));
}
TEST(FixedHeapArray, ElementAccess)
{
FixedHeapArray<int, 5> 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<int, 5> 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<int, 5> arr;
for (size_t i = 0; i < arr.size(); ++i)
arr[i] = static_cast<int>(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<int, 10> arr;
arr.fill(42);
for (size_t i = 0; i < arr.size(); ++i)
EXPECT_EQ(arr[i], 42);
}
TEST(FixedHeapArray, Swap)
{
FixedHeapArray<int, 5> arr1;
FixedHeapArray<int, 5> 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<int, 5> arr;
for (size_t i = 0; i < arr.size(); ++i)
arr[i] = static_cast<int>(i * 2);
std::span<int, 5> 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<int>(i * 2));
const auto& carr = arr;
std::span<const int, 5> csp = carr.cspan();
EXPECT_EQ(csp.size(), 5u);
EXPECT_EQ(csp.data(), carr.data());
}
TEST(FixedHeapArray, MoveAssignment)
{
FixedHeapArray<int, 5> arr1;
arr1.fill(42);
int* original_data = arr1.data();
FixedHeapArray<int, 5> 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<int, 16, alignment> arr;
uintptr_t addr = reinterpret_cast<uintptr_t>(arr.data());
EXPECT_EQ(addr % alignment, 0u);
}
TEST(FixedHeapArray, DifferentTypes)
{
FixedHeapArray<double, 3> 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<int> 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<int> 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<int> 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<int> 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<const int> sp(source);
DynamicHeapArray<int> 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<const int> sp;
DynamicHeapArray<int> arr(sp);
EXPECT_EQ(arr.size(), 0u);
EXPECT_TRUE(arr.empty());
}
TEST(DynamicHeapArray, CopyConstruction)
{
DynamicHeapArray<int> arr1(5);
for (size_t i = 0; i < arr1.size(); ++i)
arr1[i] = static_cast<int>(i * 10);
DynamicHeapArray<int> 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<int> arr1;
DynamicHeapArray<int> arr2(arr1);
EXPECT_EQ(arr2.size(), 0u);
EXPECT_TRUE(arr2.empty());
}
TEST(DynamicHeapArray, MoveConstruction)
{
DynamicHeapArray<int> arr1(5);
int* original_data = arr1.data();
for (size_t i = 0; i < arr1.size(); ++i)
arr1[i] = static_cast<int>(i * 10);
DynamicHeapArray<int> 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<int>(i * 10));
}
TEST(DynamicHeapArray, ElementAccess)
{
DynamicHeapArray<int> 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<int> 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<int> arr(5);
for (size_t i = 0; i < arr.size(); ++i)
arr[i] = static_cast<int>(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<int> arr(10);
arr.fill(42);
for (size_t i = 0; i < arr.size(); ++i)
EXPECT_EQ(arr[i], 42);
}
TEST(DynamicHeapArray, Swap)
{
DynamicHeapArray<int> arr1(5);
DynamicHeapArray<int> 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<int> arr(5);
for (size_t i = 0; i < arr.size(); ++i)
arr[i] = static_cast<int>(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<int>(i));
}
TEST(DynamicHeapArray, ResizeShrink)
{
DynamicHeapArray<int> arr(10);
for (size_t i = 0; i < arr.size(); ++i)
arr[i] = static_cast<int>(i);
arr.resize(5);
EXPECT_EQ(arr.size(), 5u);
for (size_t i = 0; i < arr.size(); ++i)
EXPECT_EQ(arr[i], static_cast<int>(i));
}
TEST(DynamicHeapArray, ResizeSameSize)
{
DynamicHeapArray<int> 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<int> 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<int> arr;
int source[] = {1, 2, 3, 4, 5};
std::span<const int> 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<int> 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<int> 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<int> arr1(5);
for (size_t i = 0; i < arr1.size(); ++i)
arr1[i] = static_cast<int>(i * 3);
DynamicHeapArray<int> 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<int> arr1(5);
int* original_data = arr1.data();
for (size_t i = 0; i < arr1.size(); ++i)
arr1[i] = static_cast<int>(i * 3);
DynamicHeapArray<int> 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<int> arr(10);
arr.fill(42);
int* empty = nullptr;
arr.assign(empty, static_cast<size_t>(0));
EXPECT_TRUE(arr.empty());
EXPECT_EQ(arr.size(), 0u);
}
TEST(DynamicHeapArray, AssignSameSize)
{
DynamicHeapArray<int> 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<int> arr(5);
for (size_t i = 0; i < arr.size(); ++i)
arr[i] = static_cast<int>(i * 2);
std::span<int> 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<int>(i * 2));
}
TEST(DynamicHeapArray, SpanWithOffset)
{
DynamicHeapArray<int> arr(10);
for (size_t i = 0; i < arr.size(); ++i)
arr[i] = static_cast<int>(i);
std::span<int> 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<int> arr(10);
for (size_t i = 0; i < arr.size(); ++i)
arr[i] = static_cast<int>(i);
// Request more than available
std::span<int> sp = arr.span(7, 100);
EXPECT_EQ(sp.size(), 3u);
EXPECT_EQ(sp[0], 7);
EXPECT_EQ(sp[2], 9);
}
TEST(DynamicHeapArray, SpanWithOffsetOutOfRange)
{
DynamicHeapArray<int> arr(5);
std::span<int> sp = arr.span(10);
EXPECT_TRUE(sp.empty());
}
TEST(DynamicHeapArray, CSpan)
{
DynamicHeapArray<int> arr(5);
for (size_t i = 0; i < arr.size(); ++i)
arr[i] = static_cast<int>(i * 2);
const auto& carr = arr;
std::span<const int> csp = carr.cspan();
EXPECT_EQ(csp.size(), 5u);
EXPECT_EQ(csp.data(), carr.data());
}
TEST(DynamicHeapArray, CSpanWithOffset)
{
DynamicHeapArray<int> arr(10);
for (size_t i = 0; i < arr.size(); ++i)
arr[i] = static_cast<int>(i);
const auto& carr = arr;
std::span<const int> csp = carr.cspan(2, 3);
EXPECT_EQ(csp.size(), 3u);
EXPECT_EQ(csp[0], 2);
EXPECT_EQ(csp[2], 4);
}
TEST(DynamicHeapArray, CopyAssignment)
{
DynamicHeapArray<int> arr1(5);
for (size_t i = 0; i < arr1.size(); ++i)
arr1[i] = static_cast<int>(i * 2);
DynamicHeapArray<int> 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<int> arr1(5);
int* original_data = arr1.data();
arr1.fill(42);
DynamicHeapArray<int> 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<int, alignment> arr(16);
uintptr_t addr = reinterpret_cast<uintptr_t>(arr.data());
EXPECT_EQ(addr % alignment, 0u);
}
TEST(DynamicHeapArray, AlignedResize)
{
constexpr size_t alignment = 64;
DynamicHeapArray<int, alignment> arr(8);
arr.fill(42);
arr.resize(32);
uintptr_t addr = reinterpret_cast<uintptr_t>(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<double> 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<int> 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<std::byte> arr(16);
for (size_t i = 0; i < arr.size(); ++i)
arr[i] = static_cast<std::byte>(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<std::byte>(i));
}
TEST(DynamicHeapArray, EmptyRangeConstruction)
{
int* empty = nullptr;
DynamicHeapArray<int> arr(empty, empty);
EXPECT_TRUE(arr.empty());
EXPECT_EQ(arr.size(), 0u);
EXPECT_EQ(arr.data(), nullptr);
}
TEST(DynamicHeapArray, EmptyCountConstruction)
{
int* ptr = nullptr;
DynamicHeapArray<int> arr(ptr, static_cast<size_t>(0));
EXPECT_TRUE(arr.empty());
EXPECT_EQ(arr.size(), 0u);
EXPECT_EQ(arr.data(), nullptr);
}
TEST(FixedHeapArray, CopyAssignmentCopiesWrongDirection)
{
FixedHeapArray<int, 4> src;
FixedHeapArray<int, 4> 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<int, 4> a;
FixedHeapArray<int, 4> 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<int, 4> a;
FixedHeapArray<int, 4> 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<int> a(4);
DynamicHeapArray<int> 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<int> a(4);
DynamicHeapArray<int> b(8);
// Different sizes should NOT be equal
EXPECT_FALSE(a == b);
}
TEST(DynamicHeapArray, InequalityOperatorWhenSizesDiffer)
{
DynamicHeapArray<int> a(4);
DynamicHeapArray<int> b(8);
// Different sizes should be not-equal
EXPECT_TRUE(a != b);
}

File diff suppressed because it is too large Load Diff

View File

@@ -13,6 +13,9 @@
template<typename T, std::size_t SIZE, std::size_t ALIGNMENT = 0>
class FixedHeapArray
{
static_assert(std::is_trivially_copyable_v<T>, "T is trivially copyable");
static_assert(std::is_standard_layout_v<T>, "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<T, SIZE> span() { return std::span<T, SIZE>(m_data); }
std::span<const T, SIZE> cspan() const { return std::span<const T, SIZE>(m_data); }
std::span<T, SIZE> span() { return std::span<T, SIZE>(m_data, m_data + SIZE); }
std::span<const T, SIZE> cspan() const { return std::span<const T, SIZE>(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)

View File

@@ -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<u32>(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<u32>(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<u32>(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<u32>(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<u32>(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<u32>(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<u32>(str.size()));
}
@@ -497,6 +514,7 @@ void SmallStringBase::assign(const std::wstring_view wstr)
}
m_length = static_cast<u32>(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<u32>(std::strlen(search));
if (search_length == 0)
return 0;
const u32 replacement_length = static_cast<u32>(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<u32>(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<u32>(offset));
DebugAssert(chars_after_offset >= search_length);
if (chars_after_offset > search_length)
{
std::memmove(&m_buffer[static_cast<u32>(offset) + replacement_length],
&m_buffer[static_cast<u32>(offset) + search_length], chars_after_offset - search_length);
std::memcpy(&m_buffer[static_cast<u32>(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

View File

@@ -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