Tests: Add more unit tests for common classes

pull/3544/head
Stenzek 2 months ago
parent db2f563db6
commit faa75991a0
No known key found for this signature in database

@ -1,10 +1,11 @@
add_executable(common-tests
bitutils_tests.cpp
file_system_tests.cpp
gsvector.cpp
gsvector_yuvtorgb_test.cpp
hash_tests.cpp
path_tests.cpp
rectangle_tests.cpp
sha256_tests.cpp
string_tests.cpp
)

@ -1,12 +1,424 @@
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
#include "common/align.h"
#include "common/bitfield.h"
#include "common/bitutils.h"
#include "gtest/gtest.h"
#include <type_traits>
// Test fixture for BitField tests
namespace {
// Simple test structs using different backing types and field configurations
union TestUnion8
{
u8 bits;
BitField<u8, bool, 0, 1> flag;
BitField<u8, u8, 1, 3> value3bit;
BitField<u8, u8, 4, 4> value4bit;
BitField<u8, s8, 4, 4> signed4bit;
};
union TestUnion16
{
u16 bits;
BitField<u16, bool, 0, 1> flag;
BitField<u16, u8, 1, 8> value8bit;
BitField<u16, u8, 9, 7> value7bit;
BitField<u16, s8, 8, 8> signed8bit;
};
union TestUnion32
{
u32 bits;
BitField<u32, bool, 0, 1> flag;
BitField<u32, u16, 1, 16> value16bit;
BitField<u32, u32, 17, 15> value15bit;
BitField<u32, s16, 16, 16> signed16bit;
};
union TestUnion64
{
u64 bits;
BitField<u64, bool, 0, 1> flag;
BitField<u64, u32, 1, 32> value32bit;
BitField<u64, u64, 33, 31> value31bit;
BitField<u64, s32, 32, 32> signed32bit;
};
} // namespace
// Tests for GetMask method
TEST(BitField, GetMask)
{
TestUnion8 test8{};
EXPECT_EQ(test8.flag.GetMask(), 0x01u);
EXPECT_EQ(test8.value3bit.GetMask(), 0x0Eu);
EXPECT_EQ(test8.value4bit.GetMask(), 0xF0u);
TestUnion16 test16{};
EXPECT_EQ(test16.flag.GetMask(), 0x0001u);
EXPECT_EQ(test16.value8bit.GetMask(), 0x01FEu);
EXPECT_EQ(test16.value7bit.GetMask(), 0xFE00u);
TestUnion32 test32{};
EXPECT_EQ(test32.flag.GetMask(), 0x00000001u);
EXPECT_EQ(test32.value16bit.GetMask(), 0x0001FFFEu);
EXPECT_EQ(test32.value15bit.GetMask(), 0xFFFE0000u);
TestUnion64 test64{};
EXPECT_EQ(test64.flag.GetMask(), 0x0000000000000001ULL);
EXPECT_EQ(test64.value32bit.GetMask(), 0x00000001FFFFFFFEULL);
EXPECT_EQ(test64.value31bit.GetMask(), 0xFFFFFFFE00000000ULL);
}
// Tests for implicit conversion operator (operator DataType())
TEST(BitField, ImplicitConversion)
{
TestUnion8 test8{};
test8.bits = 0xFF;
bool flag_value = test8.flag;
EXPECT_TRUE(flag_value);
u8 value3bit_value = test8.value3bit;
EXPECT_EQ(value3bit_value, 7);
u8 value4bit_value = test8.value4bit;
EXPECT_EQ(value4bit_value, 15);
// Test with zeros
test8.bits = 0x00;
flag_value = test8.flag;
EXPECT_FALSE(flag_value);
value3bit_value = test8.value3bit;
EXPECT_EQ(value3bit_value, 0);
}
// Tests for assignment operator (operator=)
TEST(BitField, Assignment)
{
TestUnion8 test8{};
test8.bits = 0x00;
test8.flag = true;
EXPECT_EQ(test8.bits, 0x01);
test8.value3bit = 5;
EXPECT_EQ(test8.bits, 0x0B); // 0001 (flag) + 1010 (5 << 1)
test8.value4bit = 12;
EXPECT_EQ(test8.bits, 0xCB); // Previous + 1100 (12 << 4)
// Test overwriting
test8.flag = false;
EXPECT_EQ(test8.bits, 0xCA);
}
// Tests for prefix increment operator (operator++())
TEST(BitField, PrefixIncrement)
{
TestUnion8 test8{};
test8.bits = 0x00;
test8.value3bit = 3;
u8 result = ++test8.value3bit;
EXPECT_EQ(result, 4);
EXPECT_EQ(static_cast<u8>(test8.value3bit), 4);
// Test overflow handling
test8.value3bit = 7; // Maximum for 3 bits
result = ++test8.value3bit;
EXPECT_EQ(result, 0); // Should wrap around
EXPECT_EQ(static_cast<u8>(test8.value3bit), 0);
}
// Tests for postfix increment operator (operator++(int))
TEST(BitField, PostfixIncrement)
{
TestUnion8 test8{};
test8.bits = 0x00;
test8.value3bit = 3;
u8 result = test8.value3bit++;
EXPECT_EQ(result, 3); // Should return old value
EXPECT_EQ(static_cast<u8>(test8.value3bit), 4); // Should increment
// Test overflow handling
test8.value3bit = 7; // Maximum for 3 bits
result = test8.value3bit++;
EXPECT_EQ(result, 7); // Should return old value
EXPECT_EQ(static_cast<u8>(test8.value3bit), 0); // Should wrap around
}
// Tests for prefix decrement operator (operator--())
TEST(BitField, PrefixDecrement)
{
TestUnion8 test8{};
test8.bits = 0x00;
test8.value3bit = 4;
u8 result = --test8.value3bit;
EXPECT_EQ(result, 3);
EXPECT_EQ(static_cast<u8>(test8.value3bit), 3);
// Test underflow handling
test8.value3bit = 0;
result = --test8.value3bit;
EXPECT_EQ(result, 7); // Should wrap around
EXPECT_EQ(static_cast<u8>(test8.value3bit), 7);
}
// Tests for postfix decrement operator (operator--(int))
TEST(BitField, PostfixDecrement)
{
TestUnion8 test8{};
test8.bits = 0x00;
test8.value3bit = 4;
u8 result = test8.value3bit--;
EXPECT_EQ(result, 4); // Should return old value
EXPECT_EQ(static_cast<u8>(test8.value3bit), 3); // Should decrement
// Test underflow handling
test8.value3bit = 0;
result = test8.value3bit--;
EXPECT_EQ(result, 0); // Should return old value
EXPECT_EQ(static_cast<u8>(test8.value3bit), 7); // Should wrap around
}
// Tests for compound assignment operators (+=, -=, *=, /=)
TEST(BitField, CompoundArithmeticOperators)
{
TestUnion8 test8{};
test8.bits = 0x00;
test8.value4bit = 5;
// Test +=
test8.value4bit += 3;
EXPECT_EQ(static_cast<u8>(test8.value4bit), 8);
// Test -=
test8.value4bit -= 2;
EXPECT_EQ(static_cast<u8>(test8.value4bit), 6);
// Test *=
test8.value4bit *= 2;
EXPECT_EQ(static_cast<u8>(test8.value4bit), 12);
// Test / =
test8.value4bit /= 3;
EXPECT_EQ(static_cast<u8>(test8.value4bit), 4);
// Test overflow with +=
test8.value4bit = 14;
test8.value4bit += 5; // Should overflow and wrap
EXPECT_EQ(static_cast<u8>(test8.value4bit), 3); // (14 + 5) & 0xF = 19 & 0xF = 3
}
// Tests for compound bitwise operators (&=, |=, ^=, <<=, >>=)
TEST(BitField, CompoundBitwiseOperators)
{
TestUnion8 test8{};
test8.bits = 0x00;
test8.value4bit = 12; // 1100
// Test & =
test8.value4bit &= 10; // 1100 & 1010 = 1000
EXPECT_EQ(static_cast<u8>(test8.value4bit), 8);
// Test |=
test8.value4bit |= 3; // 1000 | 0011 = 1011
EXPECT_EQ(static_cast<u8>(test8.value4bit), 11);
// Test ^ =
test8.value4bit ^= 5; // 1011 ^ 0101 = 1110
EXPECT_EQ(static_cast<u8>(test8.value4bit), 14);
// Test << =
test8.value4bit = 3; // 0011
test8.value4bit <<= 2; // 0011 << 2 = 1100
EXPECT_EQ(static_cast<u8>(test8.value4bit), 12);
// Test >> =
test8.value4bit >>= 1; // 1100 >> 1 = 0110
EXPECT_EQ(static_cast<u8>(test8.value4bit), 6);
}
// Tests for GetValue method with different data types
TEST(BitField, GetValue)
{
// Test unsigned values
TestUnion16 test16{};
test16.bits = 0xFFFF;
EXPECT_EQ(test16.flag.GetValue(), true);
EXPECT_EQ(test16.value8bit.GetValue(), 0xFF);
EXPECT_EQ(test16.value7bit.GetValue(), 0x7F);
// Test signed values
test16.bits = 0xFF00; // Set upper 8 bits
EXPECT_EQ(test16.signed8bit.GetValue(), -1); // Should be sign-extended
test16.bits = 0x7F00; // Set bit pattern for +127
EXPECT_EQ(test16.signed8bit.GetValue(), 127);
test16.bits = 0x8000; // Set bit pattern for -128
EXPECT_EQ(test16.signed8bit.GetValue(), -128);
}
// Tests for SetValue method
TEST(BitField, SetValue)
{
TestUnion16 test16{};
test16.bits = 0x0000;
test16.flag.SetValue(true);
EXPECT_EQ(test16.bits, 0x0001);
test16.value8bit.SetValue(0xAB);
EXPECT_EQ(test16.bits, 0x0157); // 0xAB << 1 | 0x0001
test16.value7bit.SetValue(0x55);
EXPECT_EQ(test16.bits, 0xAB57); // 0x55 << 9 | previous
// Test that setting doesn't affect other fields
test16.bits = 0xFFFF;
test16.flag.SetValue(false);
EXPECT_EQ(test16.bits, 0xFFFE); // Only bit 0 should be cleared
}
// Tests for boolean BitFields (1-bit fields)
TEST(BitField, BooleanBitFields)
{
TestUnion8 test8{};
test8.bits = 0x00;
// Test setting and getting boolean values
test8.flag.SetValue(true);
EXPECT_TRUE(test8.flag.GetValue());
EXPECT_TRUE(static_cast<bool>(test8.flag));
test8.flag.SetValue(false);
EXPECT_FALSE(test8.flag.GetValue());
EXPECT_FALSE(static_cast<bool>(test8.flag));
// Test that any non-zero value in the bit position is treated as true
test8.bits = 0x01;
EXPECT_TRUE(test8.flag.GetValue());
}
// Tests for signed BitFields with sign extension
TEST(BitField, SignedBitFields)
{
TestUnion32 test32{};
test32.bits = 0x00000000;
// Test positive signed values
test32.signed16bit.SetValue(12345);
EXPECT_EQ(test32.signed16bit.GetValue(), 12345);
// Test negative signed values
test32.signed16bit.SetValue(-12345);
EXPECT_EQ(test32.signed16bit.GetValue(), -12345);
// Test maximum positive value for 16-bit signed
test32.signed16bit.SetValue(32767);
EXPECT_EQ(test32.signed16bit.GetValue(), 32767);
// Test minimum negative value for 16-bit signed
test32.signed16bit.SetValue(-32768);
EXPECT_EQ(test32.signed16bit.GetValue(), -32768);
// Test sign extension with manual bit manipulation
test32.bits = 0xFFFF0000; // Set all bits in the signed field to 1
EXPECT_EQ(test32.signed16bit.GetValue(), -1);
}
// Tests for edge cases and boundary conditions
TEST(BitField, EdgeCases)
{
TestUnion64 test64{};
test64.bits = 0x0000000000000000ULL;
// Test maximum values
test64.value32bit.SetValue(0xFFFFFFFF);
EXPECT_EQ(test64.value32bit.GetValue(), 0xFFFFFFFFu);
test64.value31bit.SetValue(0x7FFFFFFF);
EXPECT_EQ(test64.value31bit.GetValue(), 0x7FFFFFFFu);
// Test that fields don't interfere with each other
test64.bits = 0x0000000000000000ULL;
test64.flag.SetValue(true);
test64.value32bit.SetValue(0x12345678);
test64.value31bit.SetValue(0x55555555);
EXPECT_TRUE(test64.flag.GetValue());
EXPECT_EQ(test64.value32bit.GetValue(), 0x12345678u);
EXPECT_EQ(test64.value31bit.GetValue(), 0x55555555u);
}
// Tests for arithmetic operations that may cause overflow
TEST(BitField, OverflowBehavior)
{
TestUnion8 test8{};
test8.bits = 0x00;
// Test 3-bit field (max value 7)
test8.value3bit.SetValue(7);
test8.value3bit += 1; // Should overflow
EXPECT_EQ(static_cast<u8>(test8.value3bit), 0);
test8.value3bit.SetValue(7);
test8.value3bit *= 2; // 7 * 2 = 14, should wrap to 6 (14 & 0x7)
EXPECT_EQ(static_cast<u8>(test8.value3bit), 6);
// Test underflow
test8.value3bit.SetValue(0);
test8.value3bit -= 1; // Should underflow
EXPECT_EQ(static_cast<u8>(test8.value3bit), 7);
}
// Tests for chaining operations
TEST(BitField, OperatorChaining)
{
TestUnion8 test8{};
test8.bits = 0x00;
// Test chaining assignment operators
auto& result = test8.value3bit = 5;
EXPECT_EQ(&result, &test8.value3bit);
EXPECT_EQ(static_cast<u8>(test8.value3bit), 5);
// Test chaining compound operators
auto& result2 = (test8.value3bit += 2);
EXPECT_EQ(&result2, &test8.value3bit);
EXPECT_EQ(static_cast<u8>(test8.value3bit), 7);
}
// Tests for static_assert conditions
TEST(BitField, StaticAssertions)
{
// These should compile without issues due to the static_assert in BitField
// Boolean fields should only be 1 bit
union TestBoolField
{
u8 bits;
BitField<u8, bool, 0, 1> valid_bool; // This should compile
};
// This would fail to compile:
// BitField<u8, bool, 0, 2> invalid_bool; // static_assert should trigger
TestBoolField test{};
test.bits = 0;
test.valid_bool = true;
EXPECT_TRUE(test.valid_bool);
}
template<typename T>
static inline constexpr unsigned ManualCountLeadingZeros(T value)
{
@ -183,4 +595,275 @@ TEST(BitUtils, ByteSwap)
EXPECT_EQ(ByteSwap<s16>(s16(0x1234)), s16(0x3412));
EXPECT_EQ(ByteSwap<s32>(s32(0x12345678)), s32(0x78563412));
EXPECT_EQ(ByteSwap<s64>(s64(0x0123456789ABCDEFll)), s64(0xEFCDAB8967452301ll));
}
// Alignment function tests
TEST(Align, IsAligned)
{
// Test with various integer types and alignments
EXPECT_TRUE(Common::IsAligned(0u, 4u));
EXPECT_TRUE(Common::IsAligned(4u, 4u));
EXPECT_TRUE(Common::IsAligned(8u, 4u));
EXPECT_TRUE(Common::IsAligned(16u, 4u));
EXPECT_FALSE(Common::IsAligned(1u, 4u));
EXPECT_FALSE(Common::IsAligned(3u, 4u));
EXPECT_FALSE(Common::IsAligned(5u, 4u));
// Test with pointer types
EXPECT_TRUE(Common::IsAligned(reinterpret_cast<uintptr_t>(nullptr), 8u));
EXPECT_TRUE(Common::IsAligned(0x1000ull, 16u));
EXPECT_FALSE(Common::IsAligned(0x1001ull, 16u));
// Test with different alignments
EXPECT_TRUE(Common::IsAligned(15u, 1u));
EXPECT_TRUE(Common::IsAligned(16u, 8u));
EXPECT_FALSE(Common::IsAligned(15u, 8u));
}
TEST(Align, AlignUp)
{
// Test basic alignment up
EXPECT_EQ(Common::AlignUp(0u, 4u), 0u);
EXPECT_EQ(Common::AlignUp(1u, 4u), 4u);
EXPECT_EQ(Common::AlignUp(3u, 4u), 4u);
EXPECT_EQ(Common::AlignUp(4u, 4u), 4u);
EXPECT_EQ(Common::AlignUp(5u, 4u), 8u);
EXPECT_EQ(Common::AlignUp(7u, 4u), 8u);
EXPECT_EQ(Common::AlignUp(8u, 4u), 8u);
// Test with larger values
EXPECT_EQ(Common::AlignUp(1000u, 64u), 1024u);
EXPECT_EQ(Common::AlignUp(1024u, 64u), 1024u);
EXPECT_EQ(Common::AlignUp(1025u, 64u), 1088u);
// Test with different types
EXPECT_EQ(Common::AlignUp(13ull, 8u), 16ull);
EXPECT_EQ(Common::AlignUp(size_t(100), 16u), size_t(112));
}
TEST(Align, AlignDown)
{
// Test basic alignment down
EXPECT_EQ(Common::AlignDown(0u, 4u), 0u);
EXPECT_EQ(Common::AlignDown(1u, 4u), 0u);
EXPECT_EQ(Common::AlignDown(3u, 4u), 0u);
EXPECT_EQ(Common::AlignDown(4u, 4u), 4u);
EXPECT_EQ(Common::AlignDown(5u, 4u), 4u);
EXPECT_EQ(Common::AlignDown(7u, 4u), 4u);
EXPECT_EQ(Common::AlignDown(8u, 4u), 8u);
// Test with larger values
EXPECT_EQ(Common::AlignDown(1000u, 64u), 960u);
EXPECT_EQ(Common::AlignDown(1024u, 64u), 1024u);
EXPECT_EQ(Common::AlignDown(1087u, 64u), 1024u);
// Test with different types
EXPECT_EQ(Common::AlignDown(13ull, 8u), 8ull);
EXPECT_EQ(Common::AlignDown(size_t(100), 16u), size_t(96));
}
TEST(Align, IsAlignedPow2)
{
// Test power-of-2 alignment checks
EXPECT_TRUE(Common::IsAlignedPow2(0u, 4u));
EXPECT_TRUE(Common::IsAlignedPow2(4u, 4u));
EXPECT_TRUE(Common::IsAlignedPow2(8u, 4u));
EXPECT_TRUE(Common::IsAlignedPow2(16u, 4u));
EXPECT_FALSE(Common::IsAlignedPow2(1u, 4u));
EXPECT_FALSE(Common::IsAlignedPow2(3u, 4u));
EXPECT_FALSE(Common::IsAlignedPow2(5u, 4u));
// Test with different power-of-2 alignments
EXPECT_TRUE(Common::IsAlignedPow2(32u, 16u));
EXPECT_TRUE(Common::IsAlignedPow2(64u, 16u));
EXPECT_TRUE(Common::IsAlignedPow2(48u, 16u));
EXPECT_FALSE(Common::IsAlignedPow2(56u, 16u));
EXPECT_FALSE(Common::IsAlignedPow2(63u, 16u));
// Test with larger values
EXPECT_TRUE(Common::IsAlignedPow2(1024u, 256u));
EXPECT_TRUE(Common::IsAlignedPow2(1280u, 256u));
EXPECT_FALSE(Common::IsAlignedPow2(1100u, 256u));
}
TEST(Align, AlignUpPow2)
{
// Test power-of-2 alignment up
EXPECT_EQ(Common::AlignUpPow2(0u, 4u), 0u);
EXPECT_EQ(Common::AlignUpPow2(1u, 4u), 4u);
EXPECT_EQ(Common::AlignUpPow2(3u, 4u), 4u);
EXPECT_EQ(Common::AlignUpPow2(4u, 4u), 4u);
EXPECT_EQ(Common::AlignUpPow2(5u, 4u), 8u);
EXPECT_EQ(Common::AlignUpPow2(7u, 4u), 8u);
EXPECT_EQ(Common::AlignUpPow2(8u, 4u), 8u);
// Test with larger power-of-2 alignments
EXPECT_EQ(Common::AlignUpPow2(1000u, 64u), 1024u);
EXPECT_EQ(Common::AlignUpPow2(1024u, 64u), 1024u);
EXPECT_EQ(Common::AlignUpPow2(1025u, 64u), 1088u);
// Test with different types
EXPECT_EQ(Common::AlignUpPow2(13ull, 8u), 16ull);
EXPECT_EQ(Common::AlignUpPow2(size_t(100), 16u), size_t(112));
}
TEST(Align, AlignDownPow2)
{
// Test power-of-2 alignment down
EXPECT_EQ(Common::AlignDownPow2(0u, 4u), 0u);
EXPECT_EQ(Common::AlignDownPow2(1u, 4u), 0u);
EXPECT_EQ(Common::AlignDownPow2(3u, 4u), 0u);
EXPECT_EQ(Common::AlignDownPow2(4u, 4u), 4u);
EXPECT_EQ(Common::AlignDownPow2(5u, 4u), 4u);
EXPECT_EQ(Common::AlignDownPow2(7u, 4u), 4u);
EXPECT_EQ(Common::AlignDownPow2(8u, 4u), 8u);
// Test with larger power-of-2 alignments
EXPECT_EQ(Common::AlignDownPow2(1000u, 64u), 960u);
EXPECT_EQ(Common::AlignDownPow2(1024u, 64u), 1024u);
EXPECT_EQ(Common::AlignDownPow2(1087u, 64u), 1024u);
// Test with different types
EXPECT_EQ(Common::AlignDownPow2(13ull, 8u), 8ull);
EXPECT_EQ(Common::AlignDownPow2(size_t(100), 16u), size_t(96));
}
TEST(Align, IsPow2)
{
// Test power-of-2 detection
EXPECT_TRUE(Common::IsPow2(0u));
EXPECT_TRUE(Common::IsPow2(1u));
EXPECT_TRUE(Common::IsPow2(2u));
EXPECT_TRUE(Common::IsPow2(4u));
EXPECT_TRUE(Common::IsPow2(8u));
EXPECT_TRUE(Common::IsPow2(16u));
EXPECT_TRUE(Common::IsPow2(32u));
EXPECT_TRUE(Common::IsPow2(64u));
EXPECT_TRUE(Common::IsPow2(128u));
EXPECT_TRUE(Common::IsPow2(256u));
EXPECT_TRUE(Common::IsPow2(1024u));
// Test non-power-of-2 values
EXPECT_FALSE(Common::IsPow2(3u));
EXPECT_FALSE(Common::IsPow2(5u));
EXPECT_FALSE(Common::IsPow2(6u));
EXPECT_FALSE(Common::IsPow2(7u));
EXPECT_FALSE(Common::IsPow2(9u));
EXPECT_FALSE(Common::IsPow2(15u));
EXPECT_FALSE(Common::IsPow2(31u));
EXPECT_FALSE(Common::IsPow2(1000u));
// Test with different types
EXPECT_TRUE(Common::IsPow2(512ull));
EXPECT_FALSE(Common::IsPow2(513ull));
}
TEST(Align, PreviousPow2)
{
// Test finding previous power-of-2
EXPECT_EQ(Common::PreviousPow2(1u), 1u);
EXPECT_EQ(Common::PreviousPow2(2u), 2u);
EXPECT_EQ(Common::PreviousPow2(3u), 2u);
EXPECT_EQ(Common::PreviousPow2(4u), 4u);
EXPECT_EQ(Common::PreviousPow2(5u), 4u);
EXPECT_EQ(Common::PreviousPow2(6u), 4u);
EXPECT_EQ(Common::PreviousPow2(7u), 4u);
EXPECT_EQ(Common::PreviousPow2(8u), 8u);
EXPECT_EQ(Common::PreviousPow2(15u), 8u);
EXPECT_EQ(Common::PreviousPow2(16u), 16u);
EXPECT_EQ(Common::PreviousPow2(31u), 16u);
EXPECT_EQ(Common::PreviousPow2(32u), 32u);
EXPECT_EQ(Common::PreviousPow2(1000u), 512u);
EXPECT_EQ(Common::PreviousPow2(1024u), 1024u);
// Test with different types
EXPECT_EQ(Common::PreviousPow2(100ull), 64ull);
EXPECT_EQ(Common::PreviousPow2(static_cast<size_t>(2047)), static_cast<size_t>(1024));
EXPECT_EQ(Common::PreviousPow2(static_cast<size_t>(2048)), static_cast<size_t>(2048));
}
TEST(Align, NextPow2)
{
// Test finding next power-of-2
EXPECT_EQ(Common::NextPow2(0u), 0u);
EXPECT_EQ(Common::NextPow2(1u), 1u);
EXPECT_EQ(Common::NextPow2(2u), 2u);
EXPECT_EQ(Common::NextPow2(3u), 4u);
EXPECT_EQ(Common::NextPow2(4u), 4u);
EXPECT_EQ(Common::NextPow2(5u), 8u);
EXPECT_EQ(Common::NextPow2(7u), 8u);
EXPECT_EQ(Common::NextPow2(8u), 8u);
EXPECT_EQ(Common::NextPow2(9u), 16u);
EXPECT_EQ(Common::NextPow2(15u), 16u);
EXPECT_EQ(Common::NextPow2(16u), 16u);
EXPECT_EQ(Common::NextPow2(17u), 32u);
EXPECT_EQ(Common::NextPow2(1000u), 1024u);
EXPECT_EQ(Common::NextPow2(1024u), 1024u);
// Test with different types
EXPECT_EQ(Common::NextPow2(100ull), 128ull);
EXPECT_EQ(Common::NextPow2(size_t(1025)), size_t(2048));
}
TEST(Align, AlignedMallocAndFree)
{
// Test aligned memory allocation and deallocation
void* ptr1 = Common::AlignedMalloc(128, 16);
ASSERT_NE(ptr1, nullptr);
EXPECT_TRUE(Common::IsAlignedPow2(reinterpret_cast<uintptr_t>(ptr1), 16u));
Common::AlignedFree(ptr1);
void* ptr2 = Common::AlignedMalloc(256, 32);
ASSERT_NE(ptr2, nullptr);
EXPECT_TRUE(Common::IsAlignedPow2(reinterpret_cast<uintptr_t>(ptr2), 32u));
Common::AlignedFree(ptr2);
void* ptr3 = Common::AlignedMalloc(1024, 64);
ASSERT_NE(ptr3, nullptr);
EXPECT_TRUE(Common::IsAlignedPow2(reinterpret_cast<uintptr_t>(ptr3), 64u));
Common::AlignedFree(ptr3);
// Test with zero size (implementation-defined behavior)
void* ptr4 = Common::AlignedMalloc(0, 16);
Common::AlignedFree(ptr4); // Should not crash even if ptr4 is nullptr
// Test AlignedFree with nullptr (should not crash)
Common::AlignedFree(nullptr);
}
TEST(Align, UniqueAlignedPtr)
{
// Test make_unique_aligned for arrays
auto ptr1 = Common::make_unique_aligned<int[]>(16, 10);
ASSERT_NE(ptr1.get(), nullptr);
EXPECT_TRUE(Common::IsAlignedPow2(reinterpret_cast<uintptr_t>(ptr1.get()), 16u));
// Test that we can write to the memory
for (int i = 0; i < 10; ++i)
{
ptr1[i] = i * 2;
}
for (int i = 0; i < 10; ++i)
{
EXPECT_EQ(ptr1[i], i * 2);
}
auto ptr2 = Common::make_unique_aligned<u8[]>(32, 100);
ASSERT_NE(ptr2.get(), nullptr);
EXPECT_TRUE(Common::IsAlignedPow2(reinterpret_cast<uintptr_t>(ptr2.get()), 32u));
// Test make_unique_aligned_for_overwrite
auto ptr3 = Common::make_unique_aligned_for_overwrite<u32[]>(64, 50);
ASSERT_NE(ptr3.get(), nullptr);
EXPECT_TRUE(Common::IsAlignedPow2(reinterpret_cast<uintptr_t>(ptr3.get()), 64u));
// Test that we can write to the memory
for (u32 i = 0; i < 50; ++i)
{
ptr3[i] = i + 1000;
}
for (u32 i = 0; i < 50; ++i)
{
EXPECT_EQ(ptr3[i], i + 1000);
}
}

@ -5,9 +5,10 @@
<ClCompile Include="..\..\dep\googletest\src\gtest_main.cc" />
<ClCompile Include="bitutils_tests.cpp" />
<ClCompile Include="file_system_tests.cpp" />
<ClCompile Include="gsvector_tests.cpp" />
<ClCompile Include="path_tests.cpp" />
<ClCompile Include="rectangle_tests.cpp" />
<ClCompile Include="sha256_tests.cpp" />
<ClCompile Include="hash_tests.cpp" />
<ClCompile Include="string_tests.cpp" />
<ClCompile Include="gsvector_yuvtorgb_test.cpp" />
</ItemGroup>

@ -8,6 +8,7 @@
<ClCompile Include="path_tests.cpp" />
<ClCompile Include="string_tests.cpp" />
<ClCompile Include="gsvector_yuvtorgb_test.cpp" />
<ClCompile Include="sha256_tests.cpp" />
<ClCompile Include="hash_tests.cpp" />
<ClCompile Include="gsvector_tests.cpp" />
</ItemGroup>
</Project>

File diff suppressed because it is too large Load Diff

@ -0,0 +1,374 @@
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
#include "common/md5_digest.h"
#include "common/sha1_digest.h"
#include "common/sha256_digest.h"
#include <gtest/gtest.h>
TEST(SHA256Digest, Simple)
{
// https://github.com/B-Con/crypto-algorithms/blob/master/sha256_test.c
static constexpr const char text1[] = "abc";
static constexpr const char text2[] = "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq";
static constexpr const char text3[] = "aaaaaaaaaa";
static constexpr SHA256Digest::Digest hash1 = {{0xba, 0x78, 0x16, 0xbf, 0x8f, 0x01, 0xcf, 0xea, 0x41, 0x41, 0x40,
0xde, 0x5d, 0xae, 0x22, 0x23, 0xb0, 0x03, 0x61, 0xa3, 0x96, 0x17,
0x7a, 0x9c, 0xb4, 0x10, 0xff, 0x61, 0xf2, 0x00, 0x15, 0xad}};
static constexpr SHA256Digest::Digest hash2 = {{0x24, 0x8d, 0x6a, 0x61, 0xd2, 0x06, 0x38, 0xb8, 0xe5, 0xc0, 0x26,
0x93, 0x0c, 0x3e, 0x60, 0x39, 0xa3, 0x3c, 0xe4, 0x59, 0x64, 0xff,
0x21, 0x67, 0xf6, 0xec, 0xed, 0xd4, 0x19, 0xdb, 0x06, 0xc1}};
static constexpr SHA256Digest::Digest hash3 = {{0xcd, 0xc7, 0x6e, 0x5c, 0x99, 0x14, 0xfb, 0x92, 0x81, 0xa1, 0xc7,
0xe2, 0x84, 0xd7, 0x3e, 0x67, 0xf1, 0x80, 0x9a, 0x48, 0xa4, 0x97,
0x20, 0x0e, 0x04, 0x6d, 0x39, 0xcc, 0xc7, 0x11, 0x2c, 0xd0}};
ASSERT_EQ(SHA256Digest::GetDigest(text1, std::size(text1) - 1), hash1);
ASSERT_EQ(SHA256Digest::GetDigest(text2, std::size(text2) - 1), hash2);
SHA256Digest ldigest;
for (u32 i = 0; i < 100000; i++)
ldigest.Update(text3, std::size(text3) - 1);
ASSERT_EQ(ldigest.Final(), hash3);
}
// MD5 Digest Tests
TEST(MD5Digest, EmptyString)
{
// MD5 hash of empty string: d41d8cd98f00b204e9800998ecf8427e
static constexpr std::array<u8, MD5Digest::DIGEST_SIZE> expected = {
{0xd4, 0x1d, 0x8c, 0xd9, 0x8f, 0x00, 0xb2, 0x04, 0xe9, 0x80, 0x09, 0x98, 0xec, 0xf8, 0x42, 0x7e}};
const std::string empty_string = "";
auto result = MD5Digest::HashData(std::span<const u8>(reinterpret_cast<const u8*>(empty_string.data()), 0));
EXPECT_EQ(result, expected);
}
TEST(MD5Digest, SingleCharacter)
{
// MD5 hash of "a": 0cc175b9c0f1b6a831c399e269772661
static constexpr std::array<u8, MD5Digest::DIGEST_SIZE> expected = {
{0x0c, 0xc1, 0x75, 0xb9, 0xc0, 0xf1, 0xb6, 0xa8, 0x31, 0xc3, 0x99, 0xe2, 0x69, 0x77, 0x26, 0x61}};
const std::string test_string = "a";
auto result =
MD5Digest::HashData(std::span<const u8>(reinterpret_cast<const u8*>(test_string.data()), test_string.size()));
EXPECT_EQ(result, expected);
}
TEST(MD5Digest, ABC)
{
// MD5 hash of "abc": 900150983cd24fb0d6963f7d28e17f72
static constexpr std::array<u8, MD5Digest::DIGEST_SIZE> expected = {
{0x90, 0x01, 0x50, 0x98, 0x3c, 0xd2, 0x4f, 0xb0, 0xd6, 0x96, 0x3f, 0x7d, 0x28, 0xe1, 0x7f, 0x72}};
const std::string test_string = "abc";
auto result =
MD5Digest::HashData(std::span<const u8>(reinterpret_cast<const u8*>(test_string.data()), test_string.size()));
EXPECT_EQ(result, expected);
}
TEST(MD5Digest, MessageDigest)
{
// MD5 hash of "message digest": f96b697d7cb7938d525a2f31aaf161d0
static constexpr std::array<u8, MD5Digest::DIGEST_SIZE> expected = {
{0xf9, 0x6b, 0x69, 0x7d, 0x7c, 0xb7, 0x93, 0x8d, 0x52, 0x5a, 0x2f, 0x31, 0xaa, 0xf1, 0x61, 0xd0}};
const std::string test_string = "message digest";
auto result =
MD5Digest::HashData(std::span<const u8>(reinterpret_cast<const u8*>(test_string.data()), test_string.size()));
EXPECT_EQ(result, expected);
}
TEST(MD5Digest, Alphabet)
{
// MD5 hash of "abcdefghijklmnopqrstuvwxyz": c3fcd3d76192e4007dfb496cca67e13b
static constexpr std::array<u8, MD5Digest::DIGEST_SIZE> expected = {
{0xc3, 0xfc, 0xd3, 0xd7, 0x61, 0x92, 0xe4, 0x00, 0x7d, 0xfb, 0x49, 0x6c, 0xca, 0x67, 0xe1, 0x3b}};
const std::string test_string = "abcdefghijklmnopqrstuvwxyz";
auto result =
MD5Digest::HashData(std::span<const u8>(reinterpret_cast<const u8*>(test_string.data()), test_string.size()));
EXPECT_EQ(result, expected);
}
TEST(MD5Digest, AlphaNumeric)
{
// MD5 hash of "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789": d174ab98d277d9f5a5611c2c9f419d9f
static constexpr std::array<u8, MD5Digest::DIGEST_SIZE> expected = {
{0xd1, 0x74, 0xab, 0x98, 0xd2, 0x77, 0xd9, 0xf5, 0xa5, 0x61, 0x1c, 0x2c, 0x9f, 0x41, 0x9d, 0x9f}};
const std::string test_string = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
auto result =
MD5Digest::HashData(std::span<const u8>(reinterpret_cast<const u8*>(test_string.data()), test_string.size()));
EXPECT_EQ(result, expected);
}
TEST(MD5Digest, LongString)
{
// MD5 hash of 1000000 'a' characters: 7707d6ae4e027c70eea2a935c2296f21
static constexpr std::array<u8, MD5Digest::DIGEST_SIZE> expected = {
{0x77, 0x07, 0xd6, 0xae, 0x4e, 0x02, 0x7c, 0x70, 0xee, 0xa2, 0xa9, 0x35, 0xc2, 0x29, 0x6f, 0x21}};
MD5Digest digest;
const char single_char = 'a';
for (int i = 0; i < 1000000; i++)
{
digest.Update(&single_char, 1);
}
std::array<u8, MD5Digest::DIGEST_SIZE> result;
digest.Final(result);
EXPECT_EQ(result, expected);
}
TEST(MD5Digest, IncrementalUpdate)
{
// Test that incremental updates produce the same result as a single update
const std::string test_string = "The quick brown fox jumps over the lazy dog";
// Single update
auto result1 =
MD5Digest::HashData(std::span<const u8>(reinterpret_cast<const u8*>(test_string.data()), test_string.size()));
// Incremental updates
MD5Digest digest;
digest.Update("The quick ", 10);
digest.Update("brown fox ", 10);
digest.Update("jumps over ", 11);
digest.Update("the lazy dog", 12);
std::array<u8, MD5Digest::DIGEST_SIZE> result2;
digest.Final(result2);
EXPECT_EQ(result1, result2);
}
TEST(MD5Digest, Reset)
{
MD5Digest digest;
const std::string test_string = "test data";
// First computation
digest.Update(test_string.data(), static_cast<u32>(test_string.size()));
std::array<u8, MD5Digest::DIGEST_SIZE> result1;
digest.Final(result1);
// Reset and compute again
digest.Reset();
digest.Update(test_string.data(), static_cast<u32>(test_string.size()));
std::array<u8, MD5Digest::DIGEST_SIZE> result2;
digest.Final(result2);
EXPECT_EQ(result1, result2);
}
TEST(MD5Digest, SpanInterface)
{
const std::string test_string = "test with span interface";
std::span<const u8> test_span(reinterpret_cast<const u8*>(test_string.data()), test_string.size());
// Test Update with span
MD5Digest digest;
digest.Update(test_span);
std::array<u8, MD5Digest::DIGEST_SIZE> result1;
digest.Final(result1);
// Test HashData with span
auto result2 = MD5Digest::HashData(test_span);
EXPECT_EQ(result1, result2);
}
// SHA1 Digest Tests
TEST(SHA1Digest, EmptyString)
{
// SHA1 hash of empty string: da39a3ee5e6b4b0d3255bfef95601890afd80709
static constexpr std::array<u8, SHA1Digest::DIGEST_SIZE> expected = {{0xda, 0x39, 0xa3, 0xee, 0x5e, 0x6b, 0x4b,
0x0d, 0x32, 0x55, 0xbf, 0xef, 0x95, 0x60,
0x18, 0x90, 0xaf, 0xd8, 0x07, 0x09}};
auto result = SHA1Digest::GetDigest("", 0);
EXPECT_EQ(result, expected);
}
TEST(SHA1Digest, ABC)
{
// SHA1 hash of "abc": a9993e364706816aba3e25717850c26c9cd0d89d
static constexpr std::array<u8, SHA1Digest::DIGEST_SIZE> expected = {{0xa9, 0x99, 0x3e, 0x36, 0x47, 0x06, 0x81,
0x6a, 0xba, 0x3e, 0x25, 0x71, 0x78, 0x50,
0xc2, 0x6c, 0x9c, 0xd0, 0xd8, 0x9d}};
const std::string test_string = "abc";
auto result = SHA1Digest::GetDigest(test_string.data(), test_string.size());
EXPECT_EQ(result, expected);
}
TEST(SHA1Digest, ABCExtended)
{
// SHA1 hash of "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq": 84983e441c3bd26ebaae4aa1f95129e5e54670f1
static constexpr std::array<u8, SHA1Digest::DIGEST_SIZE> expected = {{0x84, 0x98, 0x3e, 0x44, 0x1c, 0x3b, 0xd2,
0x6e, 0xba, 0xae, 0x4a, 0xa1, 0xf9, 0x51,
0x29, 0xe5, 0xe5, 0x46, 0x70, 0xf1}};
const std::string test_string = "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq";
auto result = SHA1Digest::GetDigest(test_string.data(), test_string.size());
EXPECT_EQ(result, expected);
}
TEST(SHA1Digest, QuickBrownFox)
{
// SHA1 hash of "The quick brown fox jumps over the lazy dog": 2fd4e1c67a2d28fced849ee1bb76e7391b93eb12
static constexpr std::array<u8, SHA1Digest::DIGEST_SIZE> expected = {{0x2f, 0xd4, 0xe1, 0xc6, 0x7a, 0x2d, 0x28,
0xfc, 0xed, 0x84, 0x9e, 0xe1, 0xbb, 0x76,
0xe7, 0x39, 0x1b, 0x93, 0xeb, 0x12}};
const std::string test_string = "The quick brown fox jumps over the lazy dog";
auto result = SHA1Digest::GetDigest(test_string.data(), test_string.size());
EXPECT_EQ(result, expected);
}
TEST(SHA1Digest, LongString)
{
// SHA1 hash of 1000000 'a' characters: 34aa973cd4c4daa4f61eeb2bdbad27316534016f
static constexpr std::array<u8, SHA1Digest::DIGEST_SIZE> expected = {{0x34, 0xaa, 0x97, 0x3c, 0xd4, 0xc4, 0xda,
0xa4, 0xf6, 0x1e, 0xeb, 0x2b, 0xdb, 0xad,
0x27, 0x31, 0x65, 0x34, 0x01, 0x6f}};
SHA1Digest digest;
const char single_char = 'a';
for (int i = 0; i < 1000000; i++)
{
digest.Update(&single_char, 1);
}
u8 result[SHA1Digest::DIGEST_SIZE];
digest.Final(result);
std::array<u8, SHA1Digest::DIGEST_SIZE> result_array;
std::copy(std::begin(result), std::end(result), result_array.begin());
EXPECT_EQ(result_array, expected);
}
TEST(SHA1Digest, IncrementalUpdate)
{
// Test that incremental updates produce the same result as a single update
const std::string test_string = "The quick brown fox jumps over the lazy dog";
// Single update
auto result1 = SHA1Digest::GetDigest(test_string.data(), test_string.size());
// Incremental updates
SHA1Digest digest;
digest.Update("The quick ", 10);
digest.Update("brown fox ", 10);
digest.Update("jumps over ", 11);
digest.Update("the lazy dog", 12);
u8 result_raw[SHA1Digest::DIGEST_SIZE];
digest.Final(result_raw);
std::array<u8, SHA1Digest::DIGEST_SIZE> result2;
std::copy(std::begin(result_raw), std::end(result_raw), result2.begin());
EXPECT_EQ(result1, result2);
}
TEST(SHA1Digest, Reset)
{
SHA1Digest digest;
const std::string test_string = "test data for reset";
// First computation
digest.Update(test_string.data(), test_string.size());
u8 result1_raw[SHA1Digest::DIGEST_SIZE];
digest.Final(result1_raw);
std::array<u8, SHA1Digest::DIGEST_SIZE> result1;
std::copy(std::begin(result1_raw), std::end(result1_raw), result1.begin());
// Reset and compute again
digest.Reset();
digest.Update(test_string.data(), test_string.size());
u8 result2_raw[SHA1Digest::DIGEST_SIZE];
digest.Final(result2_raw);
std::array<u8, SHA1Digest::DIGEST_SIZE> result2;
std::copy(std::begin(result2_raw), std::end(result2_raw), result2.begin());
EXPECT_EQ(result1, result2);
}
TEST(SHA1Digest, SpanInterface)
{
const std::string test_string = "test with span interface";
std::span<const u8> test_span(reinterpret_cast<const u8*>(test_string.data()), test_string.size());
// Test Update with span
SHA1Digest digest;
digest.Update(test_span);
u8 result1_raw[SHA1Digest::DIGEST_SIZE];
digest.Final(result1_raw);
std::array<u8, SHA1Digest::DIGEST_SIZE> result1;
std::copy(std::begin(result1_raw), std::end(result1_raw), result1.begin());
// Test GetDigest with span
auto result2 = SHA1Digest::GetDigest(test_span);
EXPECT_EQ(result1, result2);
}
TEST(SHA1Digest, DigestToString)
{
// Test the DigestToString method
const std::string test_string = "abc";
auto digest = SHA1Digest::GetDigest(test_string.data(), test_string.size());
std::span<const u8, SHA1Digest::DIGEST_SIZE> digest_span(digest);
std::string hex_string = SHA1Digest::DigestToString(digest_span);
// Expected: a9993e364706816aba3e25717850c26c9cd0d89d
EXPECT_EQ(hex_string, "a9993e364706816aba3e25717850c26c9cd0d89d");
}
TEST(SHA1Digest, BinaryData)
{
// Test with binary data containing null bytes
const u8 binary_data[] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F};
auto result1 = SHA1Digest::GetDigest(binary_data, sizeof(binary_data));
SHA1Digest digest;
digest.Update(binary_data, sizeof(binary_data));
u8 result2_raw[SHA1Digest::DIGEST_SIZE];
digest.Final(result2_raw);
std::array<u8, SHA1Digest::DIGEST_SIZE> result2;
std::copy(std::begin(result2_raw), std::end(result2_raw), result2.begin());
EXPECT_EQ(result1, result2);
}
TEST(MD5Digest, BinaryData)
{
// Test MD5 with binary data containing null bytes
const u8 binary_data[] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F};
auto result1 = MD5Digest::HashData(std::span<const u8>(binary_data, sizeof(binary_data)));
MD5Digest digest;
digest.Update(binary_data, sizeof(binary_data));
std::array<u8, MD5Digest::DIGEST_SIZE> result2;
digest.Final(result2);
EXPECT_EQ(result1, result2);
}

@ -1,34 +0,0 @@
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
#include "common/sha256_digest.h"
#include <gtest/gtest.h>
TEST(SHA256Digest, Simple)
{
// https://github.com/B-Con/crypto-algorithms/blob/master/sha256_test.c
static constexpr const char text1[] = "abc";
static constexpr const char text2[] = "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq";
static constexpr const char text3[] = "aaaaaaaaaa";
static constexpr SHA256Digest::Digest hash1 = {{0xba, 0x78, 0x16, 0xbf, 0x8f, 0x01, 0xcf, 0xea, 0x41, 0x41, 0x40,
0xde, 0x5d, 0xae, 0x22, 0x23, 0xb0, 0x03, 0x61, 0xa3, 0x96, 0x17,
0x7a, 0x9c, 0xb4, 0x10, 0xff, 0x61, 0xf2, 0x00, 0x15, 0xad}};
static constexpr SHA256Digest::Digest hash2 = {{0x24, 0x8d, 0x6a, 0x61, 0xd2, 0x06, 0x38, 0xb8, 0xe5, 0xc0, 0x26,
0x93, 0x0c, 0x3e, 0x60, 0x39, 0xa3, 0x3c, 0xe4, 0x59, 0x64, 0xff,
0x21, 0x67, 0xf6, 0xec, 0xed, 0xd4, 0x19, 0xdb, 0x06, 0xc1}};
static constexpr SHA256Digest::Digest hash3 = {{0xcd, 0xc7, 0x6e, 0x5c, 0x99, 0x14, 0xfb, 0x92, 0x81, 0xa1, 0xc7,
0xe2, 0x84, 0xd7, 0x3e, 0x67, 0xf1, 0x80, 0x9a, 0x48, 0xa4, 0x97,
0x20, 0x0e, 0x04, 0x6d, 0x39, 0xcc, 0xc7, 0x11, 0x2c, 0xd0}};
ASSERT_EQ(SHA256Digest::GetDigest(text1, std::size(text1) - 1), hash1);
ASSERT_EQ(SHA256Digest::GetDigest(text2, std::size(text2) - 1), hash2);
SHA256Digest ldigest;
for (u32 i = 0; i < 100000; i++)
ldigest.Update(text3, std::size(text3) - 1);
ASSERT_EQ(ldigest.Final(), hash3);
}

@ -127,3 +127,895 @@ TEST(StringUtil, CompareNoCase)
ASSERT_EQ(StringUtil::CompareNoCase("A", "a"), 0);
ASSERT_EQ(StringUtil::CompareNoCase("z", "Z"), 0);
}
// New tests for methods not already covered
TEST(StringUtil, ToLowerToUpper)
{
// Test ToLower
ASSERT_EQ(StringUtil::ToLower('A'), 'a');
ASSERT_EQ(StringUtil::ToLower('Z'), 'z');
ASSERT_EQ(StringUtil::ToLower('M'), 'm');
ASSERT_EQ(StringUtil::ToLower('a'), 'a'); // Already lowercase
ASSERT_EQ(StringUtil::ToLower('z'), 'z'); // Already lowercase
ASSERT_EQ(StringUtil::ToLower('1'), '1'); // Non-alphabetic
ASSERT_EQ(StringUtil::ToLower('!'), '!'); // Non-alphabetic
ASSERT_EQ(StringUtil::ToLower(' '), ' '); // Space
// Test ToUpper
ASSERT_EQ(StringUtil::ToUpper('a'), 'A');
ASSERT_EQ(StringUtil::ToUpper('z'), 'Z');
ASSERT_EQ(StringUtil::ToUpper('m'), 'M');
ASSERT_EQ(StringUtil::ToUpper('A'), 'A'); // Already uppercase
ASSERT_EQ(StringUtil::ToUpper('Z'), 'Z'); // Already uppercase
ASSERT_EQ(StringUtil::ToUpper('1'), '1'); // Non-alphabetic
ASSERT_EQ(StringUtil::ToUpper('!'), '!'); // Non-alphabetic
ASSERT_EQ(StringUtil::ToUpper(' '), ' '); // Space
}
TEST(StringUtil, WildcardMatch)
{
// Basic wildcard tests
ASSERT_TRUE(StringUtil::WildcardMatch("test", "test"));
ASSERT_TRUE(StringUtil::WildcardMatch("test", "*"));
ASSERT_TRUE(StringUtil::WildcardMatch("test", "t*"));
ASSERT_TRUE(StringUtil::WildcardMatch("test", "*t"));
ASSERT_TRUE(StringUtil::WildcardMatch("test", "te*"));
ASSERT_TRUE(StringUtil::WildcardMatch("test", "*st"));
ASSERT_TRUE(StringUtil::WildcardMatch("test", "t*t"));
ASSERT_TRUE(StringUtil::WildcardMatch("test", "?est"));
ASSERT_TRUE(StringUtil::WildcardMatch("test", "t?st"));
ASSERT_TRUE(StringUtil::WildcardMatch("test", "tes?"));
ASSERT_TRUE(StringUtil::WildcardMatch("test", "????"));
// Negative tests
ASSERT_FALSE(StringUtil::WildcardMatch("test", "best"));
ASSERT_FALSE(StringUtil::WildcardMatch("test", "tests"));
ASSERT_FALSE(StringUtil::WildcardMatch("test", "???"));
ASSERT_FALSE(StringUtil::WildcardMatch("test", "?????"));
// Case sensitivity tests
ASSERT_TRUE(StringUtil::WildcardMatch("Test", "test", false));
ASSERT_FALSE(StringUtil::WildcardMatch("Test", "test", true));
ASSERT_TRUE(StringUtil::WildcardMatch("TEST", "*est", false));
ASSERT_FALSE(StringUtil::WildcardMatch("TEST", "*est", true));
// Empty string tests
ASSERT_TRUE(StringUtil::WildcardMatch("", ""));
ASSERT_TRUE(StringUtil::WildcardMatch("", "*"));
ASSERT_FALSE(StringUtil::WildcardMatch("", "?"));
ASSERT_FALSE(StringUtil::WildcardMatch("test", ""));
}
TEST(StringUtil, Strlcpy)
{
char buffer[10];
// Normal copy
std::size_t result = StringUtil::Strlcpy(buffer, "hello", sizeof(buffer));
ASSERT_EQ(result, 5u);
ASSERT_STREQ(buffer, "hello");
// Truncation test
result = StringUtil::Strlcpy(buffer, "hello world", sizeof(buffer));
ASSERT_EQ(result, 11u); // Should return original string length
ASSERT_STREQ(buffer, "hello wor"); // Should be truncated and null-terminated
// Empty string
result = StringUtil::Strlcpy(buffer, "", sizeof(buffer));
ASSERT_EQ(result, 0u);
ASSERT_STREQ(buffer, "");
// Buffer size 1 (only null terminator)
result = StringUtil::Strlcpy(buffer, "test", 1);
ASSERT_EQ(result, 4u);
ASSERT_STREQ(buffer, "");
// Test with string_view
std::string_view sv = "test string";
result = StringUtil::Strlcpy(buffer, sv, sizeof(buffer));
ASSERT_EQ(result, 11u);
ASSERT_STREQ(buffer, "test stri");
}
TEST(StringUtil, Strnlen)
{
const char* str = "hello world";
ASSERT_EQ(StringUtil::Strnlen(str, 100), 11u);
ASSERT_EQ(StringUtil::Strnlen(str, 5), 5u);
ASSERT_EQ(StringUtil::Strnlen(str, 0), 0u);
ASSERT_EQ(StringUtil::Strnlen("", 10), 0u);
}
TEST(StringUtil, Strcasecmp)
{
ASSERT_EQ(StringUtil::Strcasecmp("hello", "hello"), 0);
ASSERT_EQ(StringUtil::Strcasecmp("Hello", "hello"), 0);
ASSERT_EQ(StringUtil::Strcasecmp("HELLO", "hello"), 0);
ASSERT_LT(StringUtil::Strcasecmp("apple", "banana"), 0);
ASSERT_GT(StringUtil::Strcasecmp("zebra", "apple"), 0);
}
TEST(StringUtil, Strncasecmp)
{
ASSERT_EQ(StringUtil::Strncasecmp("hello", "hello", 5), 0);
ASSERT_EQ(StringUtil::Strncasecmp("Hello", "hello", 5), 0);
ASSERT_EQ(StringUtil::Strncasecmp("hello world", "hello test", 5), 0);
ASSERT_NE(StringUtil::Strncasecmp("hello world", "hello test", 10), 0);
}
TEST(StringUtil, EqualNoCase)
{
ASSERT_TRUE(StringUtil::EqualNoCase("hello", "hello"));
ASSERT_TRUE(StringUtil::EqualNoCase("Hello", "hello"));
ASSERT_TRUE(StringUtil::EqualNoCase("HELLO", "hello"));
ASSERT_TRUE(StringUtil::EqualNoCase("", ""));
ASSERT_FALSE(StringUtil::EqualNoCase("hello", "world"));
ASSERT_FALSE(StringUtil::EqualNoCase("hello", "hello world"));
ASSERT_FALSE(StringUtil::EqualNoCase("hello world", "hello"));
}
TEST(StringUtil, ContainsNoCase)
{
ASSERT_TRUE(StringUtil::ContainsNoCase("hello world", "world"));
ASSERT_TRUE(StringUtil::ContainsNoCase("hello world", "WORLD"));
ASSERT_TRUE(StringUtil::ContainsNoCase("Hello World", "lo wo"));
ASSERT_TRUE(StringUtil::ContainsNoCase("test", "test"));
ASSERT_TRUE(StringUtil::ContainsNoCase("test", ""));
ASSERT_FALSE(StringUtil::ContainsNoCase("hello", "world"));
ASSERT_FALSE(StringUtil::ContainsNoCase("test", "testing"));
}
TEST(StringUtil, FromCharsIntegral)
{
// Test integers
auto result = StringUtil::FromChars<int>("123");
ASSERT_TRUE(result.has_value());
ASSERT_EQ(*result, 123);
result = StringUtil::FromChars<int>("-456");
ASSERT_TRUE(result.has_value());
ASSERT_EQ(*result, -456);
// Test hex
auto hex_result = StringUtil::FromChars<int>("FF", 16);
ASSERT_TRUE(hex_result.has_value());
ASSERT_EQ(*hex_result, 255);
// Test invalid input
auto invalid = StringUtil::FromChars<int>("abc");
ASSERT_FALSE(invalid.has_value());
// Test with endptr
std::string_view endptr;
auto endptr_result = StringUtil::FromChars<int>("123abc", 10, &endptr);
ASSERT_TRUE(endptr_result.has_value());
ASSERT_EQ(*endptr_result, 123);
ASSERT_EQ(endptr, "abc");
}
TEST(StringUtil, FromCharsWithOptionalBase)
{
// Test hex prefix
auto hex = StringUtil::FromCharsWithOptionalBase<int>("0xFF");
ASSERT_TRUE(hex.has_value());
ASSERT_EQ(*hex, 255);
// Test binary prefix
auto bin = StringUtil::FromCharsWithOptionalBase<int>("0b1010");
ASSERT_TRUE(bin.has_value());
ASSERT_EQ(*bin, 10);
// Test octal prefix
auto oct = StringUtil::FromCharsWithOptionalBase<int>("0123");
ASSERT_TRUE(oct.has_value());
ASSERT_EQ(*oct, 83); // 123 in octal = 83 in decimal
// Test decimal (no prefix)
auto dec = StringUtil::FromCharsWithOptionalBase<int>("123");
ASSERT_TRUE(dec.has_value());
ASSERT_EQ(*dec, 123);
}
TEST(StringUtil, FromCharsFloatingPoint)
{
auto result = StringUtil::FromChars<float>("123.45");
ASSERT_TRUE(result.has_value());
ASSERT_FLOAT_EQ(*result, 123.45f);
auto double_result = StringUtil::FromChars<double>("-456.789");
ASSERT_TRUE(double_result.has_value());
ASSERT_DOUBLE_EQ(*double_result, -456.789);
// Test scientific notation
auto sci = StringUtil::FromChars<double>("1.23e-4");
ASSERT_TRUE(sci.has_value());
ASSERT_DOUBLE_EQ(*sci, 0.000123);
// Test invalid
auto invalid = StringUtil::FromChars<float>("abc");
ASSERT_FALSE(invalid.has_value());
}
TEST(StringUtil, FromCharsBool)
{
// Test true values
ASSERT_TRUE(StringUtil::FromChars<bool>("true", 10).value_or(false));
ASSERT_TRUE(StringUtil::FromChars<bool>("TRUE", 10).value_or(false));
ASSERT_TRUE(StringUtil::FromChars<bool>("yes", 10).value_or(false));
ASSERT_TRUE(StringUtil::FromChars<bool>("YES", 10).value_or(false));
ASSERT_TRUE(StringUtil::FromChars<bool>("on", 10).value_or(false));
ASSERT_TRUE(StringUtil::FromChars<bool>("ON", 10).value_or(false));
ASSERT_TRUE(StringUtil::FromChars<bool>("1", 10).value_or(false));
ASSERT_TRUE(StringUtil::FromChars<bool>("enabled", 10).value_or(false));
ASSERT_TRUE(StringUtil::FromChars<bool>("ENABLED", 10).value_or(false));
// Test false values
ASSERT_FALSE(StringUtil::FromChars<bool>("false", 10).value_or(true));
ASSERT_FALSE(StringUtil::FromChars<bool>("FALSE", 10).value_or(true));
ASSERT_FALSE(StringUtil::FromChars<bool>("no", 10).value_or(true));
ASSERT_FALSE(StringUtil::FromChars<bool>("NO", 10).value_or(true));
ASSERT_FALSE(StringUtil::FromChars<bool>("off", 10).value_or(true));
ASSERT_FALSE(StringUtil::FromChars<bool>("OFF", 10).value_or(true));
ASSERT_FALSE(StringUtil::FromChars<bool>("0", 10).value_or(true));
ASSERT_FALSE(StringUtil::FromChars<bool>("disabled", 10).value_or(true));
ASSERT_FALSE(StringUtil::FromChars<bool>("DISABLED", 10).value_or(true));
// Test invalid
ASSERT_FALSE(StringUtil::FromChars<bool>("maybe", 10).has_value());
ASSERT_FALSE(StringUtil::FromChars<bool>("2", 10).has_value());
}
TEST(StringUtil, ToCharsIntegral)
{
ASSERT_EQ(StringUtil::ToChars(123), "123");
ASSERT_EQ(StringUtil::ToChars(-456), "-456");
ASSERT_EQ(StringUtil::ToChars(255, 16), "ff");
ASSERT_EQ(StringUtil::ToChars(15, 2), "1111");
}
TEST(StringUtil, ToCharsFloatingPoint)
{
std::string result = StringUtil::ToChars(123.45f);
ASSERT_FALSE(result.empty());
// Just check it's a valid representation, exact format may vary
ASSERT_NE(result.find("123"), std::string::npos);
}
TEST(StringUtil, ToCharsBool)
{
ASSERT_EQ(StringUtil::ToChars(true, 10), "true");
ASSERT_EQ(StringUtil::ToChars(false, 10), "false");
}
TEST(StringUtil, IsWhitespace)
{
ASSERT_TRUE(StringUtil::IsWhitespace(' '));
ASSERT_TRUE(StringUtil::IsWhitespace('\t'));
ASSERT_TRUE(StringUtil::IsWhitespace('\n'));
ASSERT_TRUE(StringUtil::IsWhitespace('\r'));
ASSERT_TRUE(StringUtil::IsWhitespace('\f'));
ASSERT_TRUE(StringUtil::IsWhitespace('\v'));
ASSERT_FALSE(StringUtil::IsWhitespace('a'));
ASSERT_FALSE(StringUtil::IsWhitespace('1'));
ASSERT_FALSE(StringUtil::IsWhitespace('!'));
}
TEST(StringUtil, DecodeHexDigit)
{
ASSERT_EQ(StringUtil::DecodeHexDigit('0'), 0);
ASSERT_EQ(StringUtil::DecodeHexDigit('9'), 9);
ASSERT_EQ(StringUtil::DecodeHexDigit('a'), 10);
ASSERT_EQ(StringUtil::DecodeHexDigit('f'), 15);
ASSERT_EQ(StringUtil::DecodeHexDigit('A'), 10);
ASSERT_EQ(StringUtil::DecodeHexDigit('F'), 15);
ASSERT_EQ(StringUtil::DecodeHexDigit('g'), 0); // Invalid should return 0
}
TEST(StringUtil, EncodeHex)
{
std::vector<u8> data = {0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF};
std::string hex = StringUtil::EncodeHex(data.data(), data.size());
ASSERT_EQ(hex, "0123456789abcdef");
// Test with span
std::string hex_span = StringUtil::EncodeHex(std::span<const u8>(data));
ASSERT_EQ(hex_span, "0123456789abcdef");
// Test empty
std::string empty_hex = StringUtil::EncodeHex(nullptr, 0);
ASSERT_EQ(empty_hex, "");
}
TEST(StringUtil, DecodeHex)
{
// Test buffer version
std::vector<u8> buffer(8);
size_t decoded = StringUtil::DecodeHex(std::span<u8>(buffer), "0123456789ABCDEF");
ASSERT_EQ(decoded, 8u);
ASSERT_EQ(buffer[0], 0x01u);
ASSERT_EQ(buffer[1], 0x23u);
ASSERT_EQ(buffer[7], 0xEFu);
// Test vector version
auto result = StringUtil::DecodeHex("0123456789ABCDEF");
ASSERT_TRUE(result.has_value());
ASSERT_EQ(result->size(), 8u);
ASSERT_EQ((*result)[0], 0x01u);
ASSERT_EQ((*result)[7], 0xEFu);
// Test invalid hex
auto invalid = StringUtil::DecodeHex("xyz");
ASSERT_FALSE(invalid.has_value());
}
TEST(StringUtil, IsHexDigit)
{
ASSERT_TRUE(StringUtil::IsHexDigit('0'));
ASSERT_TRUE(StringUtil::IsHexDigit('9'));
ASSERT_TRUE(StringUtil::IsHexDigit('a'));
ASSERT_TRUE(StringUtil::IsHexDigit('f'));
ASSERT_TRUE(StringUtil::IsHexDigit('A'));
ASSERT_TRUE(StringUtil::IsHexDigit('F'));
ASSERT_FALSE(StringUtil::IsHexDigit('g'));
ASSERT_FALSE(StringUtil::IsHexDigit('G'));
ASSERT_FALSE(StringUtil::IsHexDigit('!'));
ASSERT_FALSE(StringUtil::IsHexDigit(' '));
}
TEST(StringUtil, ParseFixedHexString)
{
constexpr auto result = StringUtil::ParseFixedHexString<4>("01234567");
ASSERT_EQ(result[0], 0x01);
ASSERT_EQ(result[1], 0x23);
ASSERT_EQ(result[2], 0x45);
ASSERT_EQ(result[3], 0x67);
}
TEST(StringUtil, Base64Lengths)
{
ASSERT_EQ(StringUtil::DecodedBase64Length(""), 0u);
ASSERT_EQ(StringUtil::DecodedBase64Length("SGVsbG8="), 5u);
ASSERT_EQ(StringUtil::DecodedBase64Length("SGVsbG8h"), 6u);
ASSERT_EQ(StringUtil::DecodedBase64Length("abc"), 0u); // Invalid length
std::vector<u8> data = {1, 2, 3, 4, 5};
ASSERT_EQ(StringUtil::EncodedBase64Length(std::span<const u8>(data)), 8u);
}
TEST(StringUtil, StartsWithNoCase)
{
ASSERT_TRUE(StringUtil::StartsWithNoCase("Hello World", "hello"));
ASSERT_TRUE(StringUtil::StartsWithNoCase("Hello World", "HELLO"));
ASSERT_TRUE(StringUtil::StartsWithNoCase("test", "test"));
ASSERT_TRUE(StringUtil::StartsWithNoCase("test", ""));
ASSERT_FALSE(StringUtil::StartsWithNoCase("Hello", "world"));
ASSERT_FALSE(StringUtil::StartsWithNoCase("Hi", "Hello"));
ASSERT_FALSE(StringUtil::StartsWithNoCase("", "test"));
}
TEST(StringUtil, EndsWithNoCase)
{
ASSERT_TRUE(StringUtil::EndsWithNoCase("Hello World", "world"));
ASSERT_TRUE(StringUtil::EndsWithNoCase("Hello World", "WORLD"));
ASSERT_TRUE(StringUtil::EndsWithNoCase("test", "test"));
ASSERT_TRUE(StringUtil::EndsWithNoCase("test", ""));
ASSERT_FALSE(StringUtil::EndsWithNoCase("Hello", "world"));
ASSERT_FALSE(StringUtil::EndsWithNoCase("Hi", "Hello"));
ASSERT_FALSE(StringUtil::EndsWithNoCase("", "test"));
}
TEST(StringUtil, StripWhitespace)
{
// Test string_view version
ASSERT_EQ(StringUtil::StripWhitespace(" hello "), "hello");
ASSERT_EQ(StringUtil::StripWhitespace("\t\n hello world \r\f"), "hello world");
ASSERT_EQ(StringUtil::StripWhitespace(" "), "");
ASSERT_EQ(StringUtil::StripWhitespace(""), "");
ASSERT_EQ(StringUtil::StripWhitespace("hello"), "hello");
ASSERT_EQ(StringUtil::StripWhitespace(" hello"), "hello");
ASSERT_EQ(StringUtil::StripWhitespace("hello "), "hello");
// Test in-place version
std::string s = " hello world ";
StringUtil::StripWhitespace(&s);
ASSERT_EQ(s, "hello world");
s = "\t\n test \r\f";
StringUtil::StripWhitespace(&s);
ASSERT_EQ(s, "test");
s = " ";
StringUtil::StripWhitespace(&s);
ASSERT_EQ(s, "");
}
TEST(StringUtil, SplitString)
{
auto result = StringUtil::SplitString("a,b,c", ',');
ASSERT_EQ(result.size(), 3u);
ASSERT_EQ(result[0], "a");
ASSERT_EQ(result[1], "b");
ASSERT_EQ(result[2], "c");
// Test with empty parts
result = StringUtil::SplitString("a,,c", ',', false);
ASSERT_EQ(result.size(), 3u);
ASSERT_EQ(result[1], "");
// Test skip empty
result = StringUtil::SplitString("a,,c", ',', true);
ASSERT_EQ(result.size(), 2u);
ASSERT_EQ(result[0], "a");
ASSERT_EQ(result[1], "c");
// Test empty string
result = StringUtil::SplitString("", ',');
ASSERT_EQ(result.size(), 0u);
// Test no delimiter
result = StringUtil::SplitString("hello", ',');
ASSERT_EQ(result.size(), 1u);
ASSERT_EQ(result[0], "hello");
}
TEST(StringUtil, SplitNewString)
{
auto result = StringUtil::SplitNewString("a,b,c", ',');
ASSERT_EQ(result.size(), 3u);
ASSERT_EQ(result[0], "a");
ASSERT_EQ(result[1], "b");
ASSERT_EQ(result[2], "c");
// Test empty string
result = StringUtil::SplitNewString("", ',');
ASSERT_EQ(result.size(), 0u);
}
TEST(StringUtil, IsInStringList)
{
std::vector<std::string> list = {"apple", "banana", "cherry"};
ASSERT_TRUE(StringUtil::IsInStringList(list, "apple"));
ASSERT_TRUE(StringUtil::IsInStringList(list, "banana"));
ASSERT_FALSE(StringUtil::IsInStringList(list, "grape"));
ASSERT_FALSE(StringUtil::IsInStringList(list, ""));
std::vector<std::string> empty_list;
ASSERT_FALSE(StringUtil::IsInStringList(empty_list, "apple"));
}
TEST(StringUtil, AddToStringList)
{
std::vector<std::string> list = {"apple", "banana"};
// Add new item
ASSERT_TRUE(StringUtil::AddToStringList(list, "cherry"));
ASSERT_EQ(list.size(), 3u);
ASSERT_EQ(list[2], "cherry");
// Try to add existing item
ASSERT_FALSE(StringUtil::AddToStringList(list, "apple"));
ASSERT_EQ(list.size(), 3u);
}
TEST(StringUtil, RemoveFromStringList)
{
std::vector<std::string> list = {"apple", "banana", "apple", "cherry"};
// Remove existing item (should remove all occurrences)
ASSERT_TRUE(StringUtil::RemoveFromStringList(list, "apple"));
ASSERT_EQ(list.size(), 2u);
ASSERT_EQ(list[0], "banana");
ASSERT_EQ(list[1], "cherry");
// Try to remove non-existing item
ASSERT_FALSE(StringUtil::RemoveFromStringList(list, "grape"));
ASSERT_EQ(list.size(), 2u);
}
TEST(StringUtil, JoinString)
{
std::vector<std::string> list = {"apple", "banana", "cherry"};
// Test with char delimiter
ASSERT_EQ(StringUtil::JoinString(list, ','), "apple,banana,cherry");
ASSERT_EQ(StringUtil::JoinString(list, ' '), "apple banana cherry");
// Test with string delimiter
ASSERT_EQ(StringUtil::JoinString(list, ", "), "apple, banana, cherry");
ASSERT_EQ(StringUtil::JoinString(list, " and "), "apple and banana and cherry");
// Test with iterator range
ASSERT_EQ(StringUtil::JoinString(list.begin(), list.end(), ','), "apple,banana,cherry");
// Test empty list
std::vector<std::string> empty_list;
ASSERT_EQ(StringUtil::JoinString(empty_list, ','), "");
// Test single item
std::vector<std::string> single = {"apple"};
ASSERT_EQ(StringUtil::JoinString(single, ','), "apple");
}
TEST(StringUtil, ReplaceAll)
{
// Test string return version
ASSERT_EQ(StringUtil::ReplaceAll("hello world", "world", "universe"), "hello universe");
ASSERT_EQ(StringUtil::ReplaceAll("test test test", "test", "exam"), "exam exam exam");
ASSERT_EQ(StringUtil::ReplaceAll("abcdef", "xyz", "123"), "abcdef"); // No match
ASSERT_EQ(StringUtil::ReplaceAll("", "test", "exam"), "");
ASSERT_EQ(StringUtil::ReplaceAll("test", "", "exam"), "test"); // Empty search
// Test in-place version
std::string s = "hello world";
StringUtil::ReplaceAll(&s, "world", "universe");
ASSERT_EQ(s, "hello universe");
// Test char versions
ASSERT_EQ(StringUtil::ReplaceAll("a,b,c", ',', ';'), "a;b;c");
s = "a,b,c";
StringUtil::ReplaceAll(&s, ',', ';');
ASSERT_EQ(s, "a;b;c");
}
TEST(StringUtil, ParseAssignmentString)
{
std::string_view key, value;
// Test normal assignment
ASSERT_TRUE(StringUtil::ParseAssignmentString("key=value", &key, &value));
ASSERT_EQ(key, "key");
ASSERT_EQ(value, "value");
// Test with spaces
ASSERT_TRUE(StringUtil::ParseAssignmentString(" key = value ", &key, &value));
ASSERT_EQ(key, "key");
ASSERT_EQ(value, "value");
// Test empty value
ASSERT_TRUE(StringUtil::ParseAssignmentString("key=", &key, &value));
ASSERT_EQ(key, "key");
ASSERT_EQ(value, "");
// Test no equals sign
ASSERT_FALSE(StringUtil::ParseAssignmentString("keyvalue", &key, &value));
// Test empty string
ASSERT_FALSE(StringUtil::ParseAssignmentString("", &key, &value));
// Test only equals
ASSERT_TRUE(StringUtil::ParseAssignmentString("=", &key, &value));
ASSERT_EQ(key, "");
ASSERT_EQ(value, "");
}
TEST(StringUtil, GetNextToken)
{
std::string_view caret = "a,b,c,d";
auto token = StringUtil::GetNextToken(caret, ',');
ASSERT_TRUE(token.has_value());
ASSERT_EQ(*token, "a");
ASSERT_EQ(caret, "b,c,d");
token = StringUtil::GetNextToken(caret, ',');
ASSERT_TRUE(token.has_value());
ASSERT_EQ(*token, "b");
ASSERT_EQ(caret, "c,d");
token = StringUtil::GetNextToken(caret, ',');
ASSERT_TRUE(token.has_value());
ASSERT_EQ(*token, "c");
ASSERT_EQ(caret, "d");
token = StringUtil::GetNextToken(caret, ',');
ASSERT_FALSE(token.has_value());
ASSERT_EQ(caret, "d");
}
TEST(StringUtil, EncodeAndAppendUTF8)
{
std::string s;
// Test ASCII character
StringUtil::EncodeAndAppendUTF8(s, U'A');
ASSERT_EQ(s, "A");
// Test 2-byte UTF-8
s.clear();
StringUtil::EncodeAndAppendUTF8(s, U'ñ'); // U+00F1
ASSERT_EQ(s.size(), 2u);
// Test 3-byte UTF-8
s.clear();
StringUtil::EncodeAndAppendUTF8(s, U''); // U+20AC
ASSERT_EQ(s.size(), 3u);
// Test 4-byte UTF-8
s.clear();
StringUtil::EncodeAndAppendUTF8(s, U'💖'); // U+1F496
ASSERT_EQ(s.size(), 4u);
// Test invalid character (should encode replacement character)
s.clear();
StringUtil::EncodeAndAppendUTF8(s, 0x110000); // Invalid
ASSERT_EQ(s.size(), 3u); // Replacement character is 3 bytes
// Test buffer version
u8 buffer[10] = {0};
size_t written = StringUtil::EncodeAndAppendUTF8(buffer, 0, sizeof(buffer), U'A');
ASSERT_EQ(written, 1u);
ASSERT_EQ(buffer[0], 'A');
written = StringUtil::EncodeAndAppendUTF8(buffer, 1, sizeof(buffer), U'');
ASSERT_EQ(written, 3u);
// Test buffer overflow
written = StringUtil::EncodeAndAppendUTF8(buffer, 9, sizeof(buffer), U'💖');
ASSERT_EQ(written, 0u); // Should fail due to insufficient space
}
TEST(StringUtil, GetEncodedUTF8Length)
{
ASSERT_EQ(StringUtil::GetEncodedUTF8Length(U'A'), 1u); // ASCII
ASSERT_EQ(StringUtil::GetEncodedUTF8Length(U'ñ'), 2u); // 2-byte
ASSERT_EQ(StringUtil::GetEncodedUTF8Length(U''), 3u); // 3-byte
ASSERT_EQ(StringUtil::GetEncodedUTF8Length(U'💖'), 4u); // 4-byte
ASSERT_EQ(StringUtil::GetEncodedUTF8Length(0x110000), 3u); // Invalid -> replacement
}
TEST(StringUtil, DecodeUTF8)
{
// Test ASCII
char32_t ch;
size_t len = StringUtil::DecodeUTF8("A", 0, &ch);
ASSERT_EQ(len, 1u);
ASSERT_EQ(ch, U'A');
// Test 2-byte UTF-8 (ñ = C3 B1)
std::string utf8_2byte = "\xC3\xB1";
len = StringUtil::DecodeUTF8(utf8_2byte, 0, &ch);
ASSERT_EQ(len, 2u);
ASSERT_EQ(ch, U'ñ');
// Test 3-byte UTF-8 (€ = E2 82 AC)
std::string utf8_3byte = "\xE2\x82\xAC";
len = StringUtil::DecodeUTF8(utf8_3byte, 0, &ch);
ASSERT_EQ(len, 3u);
ASSERT_EQ(ch, U'');
// Test void* version
len = StringUtil::DecodeUTF8(utf8_3byte.data(), utf8_3byte.size(), &ch);
ASSERT_EQ(len, 3u);
ASSERT_EQ(ch, U'');
// Test invalid UTF-8 sequence
std::string invalid_utf8 = "\xFF\xFE";
len = StringUtil::DecodeUTF8(invalid_utf8.data(), invalid_utf8.size(), &ch);
ASSERT_EQ(len, 1u);
ASSERT_EQ(ch, StringUtil::UNICODE_REPLACEMENT_CHARACTER);
}
TEST(StringUtil, EncodeAndAppendUTF16)
{
// Test ASCII character
u16 buffer[10] = {0};
size_t written = StringUtil::EncodeAndAppendUTF16(buffer, 0, 10, U'A');
ASSERT_EQ(written, 1u);
ASSERT_EQ(buffer[0], u16('A'));
// Test basic multi-byte character
written = StringUtil::EncodeAndAppendUTF16(buffer, 1, 10, U''); // U+20AC
ASSERT_EQ(written, 1u);
ASSERT_EQ(buffer[1], u16(0x20AC));
// Test surrogate pair (4-byte UTF-8 character)
written = StringUtil::EncodeAndAppendUTF16(buffer, 2, 10, U'💖'); // U+1F496
ASSERT_EQ(written, 2u);
// Should encode as surrogate pair: High surrogate D83D, Low surrogate DC96
ASSERT_EQ(buffer[2], u16(0xD83D));
ASSERT_EQ(buffer[3], u16(0xDC96));
// Test invalid surrogate range (should become replacement character)
written = StringUtil::EncodeAndAppendUTF16(buffer, 4, 10, 0xD800); // In surrogate range
ASSERT_EQ(written, 1u);
ASSERT_EQ(buffer[4], u16(StringUtil::UNICODE_REPLACEMENT_CHARACTER));
// Test invalid codepoint (should become replacement character)
written = StringUtil::EncodeAndAppendUTF16(buffer, 5, 10, 0x110000); // Invalid codepoint
ASSERT_EQ(written, 1u);
ASSERT_EQ(buffer[5], u16(StringUtil::UNICODE_REPLACEMENT_CHARACTER));
// Test buffer overflow
written = StringUtil::EncodeAndAppendUTF16(buffer, 9, 10, U'💖'); // Needs 2 units but only 1 available
ASSERT_EQ(written, 0u);
}
TEST(StringUtil, DecodeUTF16)
{
// Test ASCII character
u16 ascii_data[] = {u16('A')};
char32_t ch;
size_t len = StringUtil::DecodeUTF16(ascii_data, 0, 1, &ch);
ASSERT_EQ(len, 1u);
ASSERT_EQ(ch, U'A');
// Test basic multi-byte character
u16 euro_data[] = {u16(0x20AC)}; // €
len = StringUtil::DecodeUTF16(euro_data, 0, 1, &ch);
ASSERT_EQ(len, 1u);
ASSERT_EQ(ch, U'');
// Test surrogate pair
u16 emoji_data[] = {u16(0xD83D), u16(0xDC96)}; // 💖
len = StringUtil::DecodeUTF16(emoji_data, 0, 2, &ch);
ASSERT_EQ(len, 2u);
ASSERT_EQ(ch, U'💖');
// Test invalid high surrogate (missing low surrogate)
u16 invalid_high[] = {u16(0xD83D)};
len = StringUtil::DecodeUTF16(invalid_high, 0, 1, &ch);
ASSERT_EQ(len, 1u);
ASSERT_EQ(ch, StringUtil::UNICODE_REPLACEMENT_CHARACTER);
// Test invalid high surrogate followed by invalid low surrogate
u16 invalid_surrogates[] = {u16(0xD83D), u16(0x0041)}; // High surrogate followed by 'A'
len = StringUtil::DecodeUTF16(invalid_surrogates, 0, 2, &ch);
ASSERT_EQ(len, 2u);
ASSERT_EQ(ch, StringUtil::UNICODE_REPLACEMENT_CHARACTER);
}
TEST(StringUtil, DecodeUTF16BE)
{
// Test with byte-swapped data (big-endian)
alignas(alignof(u16)) static constexpr const u8 be_data[] = {0x20, 0xAC}; // 0x20AC (€) byte-swapped
char32_t ch;
size_t len = StringUtil::DecodeUTF16BE(be_data, 0, sizeof(be_data), &ch);
ASSERT_EQ(len, 1u);
ASSERT_EQ(ch, U'');
// Test surrogate pair with byte swapping
alignas(alignof(u16)) static constexpr const u8 be_emoji_data[] = {0xD8, 0x3D, 0xDC, 0x96}; // D83D DC96 byte-swapped
len = StringUtil::DecodeUTF16BE(be_emoji_data, 0, 2, &ch);
ASSERT_EQ(len, 2u);
ASSERT_EQ(ch, U'💖');
}
TEST(StringUtil, DecodeUTF16String)
{
// Test simple ASCII string
u16 ascii_utf16[] = {u16('H'), u16('e'), u16('l'), u16('l'), u16('o')};
std::string result = StringUtil::DecodeUTF16String(ascii_utf16, sizeof(ascii_utf16));
ASSERT_EQ(result, "Hello");
// Test string with multi-byte characters
u16 mixed_utf16[] = {u16('H'), u16('e'), u16('l'), u16('l'), u16('o'), u16(0x20AC)}; // Hello€
result = StringUtil::DecodeUTF16String(mixed_utf16, sizeof(mixed_utf16));
ASSERT_EQ(result.size(), 8u); // 5 ASCII + 3 bytes for €
ASSERT_TRUE(result.starts_with("Hello"));
// Test with surrogate pairs
u16 emoji_utf16[] = {u16('H'), u16('i'), u16(0xD83D), u16(0xDC96)}; // Hi💖
result = StringUtil::DecodeUTF16String(emoji_utf16, sizeof(emoji_utf16));
ASSERT_EQ(result.size(), 6u); // 2 ASCII + 4 bytes for 💖
ASSERT_TRUE(result.starts_with("Hi"));
}
TEST(StringUtil, DecodeUTF16BEString)
{
// Test with byte-swapped data
u16 be_utf16[] = {0x4800, 0x6500}; // "He" in big-endian
std::string result = StringUtil::DecodeUTF16BEString(be_utf16, sizeof(be_utf16));
ASSERT_EQ(result, "He");
// Test with multi-byte character
u16 be_euro[] = {0x3D20}; // € in big-endian
result = StringUtil::DecodeUTF16BEString(be_euro, sizeof(be_euro));
ASSERT_EQ(result.size(), 3u); // € is 3 bytes in UTF-8
}
TEST(StringUtil, BytePatternSearch)
{
std::vector<u8> data = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08};
// Test exact match
auto result = StringUtil::BytePatternSearch(std::span<const u8>(data), "01 02 03");
ASSERT_TRUE(result.has_value());
ASSERT_EQ(result.value(), 0u);
// Test match in middle
result = StringUtil::BytePatternSearch(std::span<const u8>(data), "03 04 05");
ASSERT_TRUE(result.has_value());
ASSERT_EQ(result.value(), 2u);
// Test with wildcards
result = StringUtil::BytePatternSearch(std::span<const u8>(data), "01 ?? 03");
ASSERT_TRUE(result.has_value());
ASSERT_EQ(result.value(), 0u);
// Test no match
result = StringUtil::BytePatternSearch(std::span<const u8>(data), "FF FF FF");
ASSERT_FALSE(result.has_value());
// Test empty pattern
result = StringUtil::BytePatternSearch(std::span<const u8>(data), "");
ASSERT_FALSE(result.has_value());
// Test lowercase hex
result = StringUtil::BytePatternSearch(std::span<const u8>(data), "01 02 03");
ASSERT_TRUE(result.has_value());
ASSERT_EQ(result.value(), 0u);
// Test mixed case
result = StringUtil::BytePatternSearch(std::span<const u8>(data), "01 ?? 03");
ASSERT_TRUE(result.has_value());
ASSERT_EQ(result.value(), 0u);
}
TEST(StringUtil, StrideMemCpy)
{
static constexpr const u8 src[] = {1, 2, 3, 4, 5, 6, 7, 8};
u8 dst[8] = {0};
// Test normal memcpy (same stride and copy size)
StringUtil::StrideMemCpy(dst, 2, src, 2, 2, 4);
ASSERT_EQ(dst[0], 1);
ASSERT_EQ(dst[1], 2);
ASSERT_EQ(dst[2], 3);
ASSERT_EQ(dst[3], 4);
// Reset and test different strides
memset(dst, 0, sizeof(dst));
StringUtil::StrideMemCpy(dst, 3, src, 2, 1, 3);
ASSERT_EQ(dst[0], 1);
ASSERT_EQ(dst[3], 3);
ASSERT_EQ(dst[6], 5);
}
TEST(StringUtil, StrideMemCmp)
{
static constexpr const u8 data1[] = {1, 0, 2, 0, 3, 0};
u8 data2[] = {1, 2, 3};
// Test equal comparison with different strides
int result = StringUtil::StrideMemCmp(data1, 2, data2, 1, 1, 3);
ASSERT_EQ(result, 0);
// Test unequal comparison
data2[1] = 4;
result = StringUtil::StrideMemCmp(data1, 2, data2, 1, 1, 3);
ASSERT_NE(result, 0);
}
#ifdef _WIN32
TEST(StringUtil, UTF8StringToWideString)
{
std::wstring result = StringUtil::UTF8StringToWideString("Hello");
ASSERT_EQ(result, L"Hello");
// Test with UTF-8 characters
std::wstring utf8_result = StringUtil::UTF8StringToWideString("Héllo");
ASSERT_FALSE(utf8_result.empty());
// Test bool version
std::wstring dest;
bool success = StringUtil::UTF8StringToWideString(dest, "Hello");
ASSERT_TRUE(success);
ASSERT_EQ(dest, L"Hello");
}
TEST(StringUtil, WideStringToUTF8String)
{
std::string result = StringUtil::WideStringToUTF8String(L"Hello");
ASSERT_EQ(result, "Hello");
// Test bool version
std::string dest;
bool success = StringUtil::WideStringToUTF8String(dest, L"Hello");
ASSERT_TRUE(success);
ASSERT_EQ(dest, "Hello");
}
#endif

@ -1846,26 +1846,6 @@ public:
return GSVector4i(vreinterpretq_s32_s64(vshlq_s64(vreinterpretq_s64_s32(v4s), vreinterpretq_s64_s32(v.v4s))));
}
template<int i>
ALWAYS_INLINE GSVector4i sra64() const
{
return GSVector4i(vreinterpretq_s32_s64(vshrq_n_s64(vreinterpretq_s64_s32(v4s), i)));
}
ALWAYS_INLINE GSVector4i sra64(s32 i) const
{
return GSVector4i(vreinterpretq_s32_s64(vshlq_s64(vreinterpretq_s64_s32(v4s), vdupq_n_s16(-i))));
}
#ifdef CPU_ARCH_ARM64
// not on arm32, hopefully we can do without
ALWAYS_INLINE GSVector4i srav64(const GSVector4i& v) const
{
return GSVector4i(
vreinterpretq_s32_s64(vshlq_s64(vreinterpretq_s64_s32(v4s), vnegq_s64(vreinterpretq_s64_s32(v.v4s)))));
}
#endif
template<int i>
ALWAYS_INLINE GSVector4i srl64() const
{

@ -1377,16 +1377,6 @@ public:
GSVector4i srlv64(const GSVector4i& v) const { ALL_LANES_64(ret.U64[i] = U64[i] >> v.U64[i]); }
template<s64 v>
GSVector4i sra64() const
{
ALL_LANES_64(ret.S64[i] = S64[i] >> v);
}
GSVector4i sra64(s32 v) const { ALL_LANES_64(ret.S64[i] = S64[i] >> v); }
GSVector4i srav64(const GSVector4i& v) const { ALL_LANES_64(ret.S64[i] = S64[i] >> v.S64[i]); }
GSVector4i add8(const GSVector4i& v) const { ALL_LANES_8(ret.S8[i] = S8[i] + v.S8[i]); }
GSVector4i add16(const GSVector4i& v) const { ALL_LANES_16(ret.S16[i] = S16[i] + v.S16[i]); }

@ -1538,18 +1538,6 @@ public:
ALWAYS_INLINE GSVector4i srlv64(const GSVector4i& v) const { return GSVector4i(_mm_srlv_epi64(m, v.m)); }
#endif
template<s64 i>
ALWAYS_INLINE GSVector4i sra64() const
{
return GSVector4i(_mm_srai_epi64(m, i));
}
ALWAYS_INLINE GSVector4i sra64(s32 i) const { return GSVector4i(_mm_sra_epi64(m, _mm_cvtsi32_si128(i))); }
#ifdef CPU_ARCH_AVX2
ALWAYS_INLINE GSVector4i srav64(const GSVector4i& v) const { return GSVector4i(_mm_srav_epi64(m, v.m)); }
#endif
ALWAYS_INLINE GSVector4i add8(const GSVector4i& v) const { return GSVector4i(_mm_add_epi8(m, v.m)); }
ALWAYS_INLINE GSVector4i add16(const GSVector4i& v) const { return GSVector4i(_mm_add_epi16(m, v.m)); }
ALWAYS_INLINE GSVector4i add32(const GSVector4i& v) const { return GSVector4i(_mm_add_epi32(m, v.m)); }
@ -2529,6 +2517,19 @@ public:
return GSVector4(_mm_broadcastss_ps(_mm_load_ss(static_cast<const float*>(f))));
}
#else
ALWAYS_INLINE GSVector4 broadcast32() const { return GSVector4(_mm_shuffle_ps(m, m, _MM_SHUFFLE(0, 0, 0, 0))); }
ALWAYS_INLINE static GSVector4 broadcast32(const GSVector4& v)
{
return GSVector4(_mm_shuffle_ps(v.m, v.m, _MM_SHUFFLE(0, 0, 0, 0)));
}
ALWAYS_INLINE static GSVector4 broadcast32(const void* f)
{
const __m128 v = _mm_load_ss(static_cast<const float*>(f));
return GSVector4(_mm_shuffle_ps(v, v, _MM_SHUFFLE(0, 0, 0, 0)));
}
#endif
ALWAYS_INLINE static GSVector4 broadcast64(const void* d)

Loading…
Cancel
Save