diff --git a/src/common-tests/CMakeLists.txt b/src/common-tests/CMakeLists.txt index 4badd66ba..8280a895c 100644 --- a/src/common-tests/CMakeLists.txt +++ b/src/common-tests/CMakeLists.txt @@ -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 ) diff --git a/src/common-tests/bitutils_tests.cpp b/src/common-tests/bitutils_tests.cpp index 2af63bcd9..8eeb4e0e8 100644 --- a/src/common-tests/bitutils_tests.cpp +++ b/src/common-tests/bitutils_tests.cpp @@ -1,12 +1,424 @@ // SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin // 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 +// Test fixture for BitField tests +namespace { +// Simple test structs using different backing types and field configurations +union TestUnion8 +{ + u8 bits; + BitField flag; + BitField value3bit; + BitField value4bit; + BitField signed4bit; +}; + +union TestUnion16 +{ + u16 bits; + BitField flag; + BitField value8bit; + BitField value7bit; + BitField signed8bit; +}; + +union TestUnion32 +{ + u32 bits; + BitField flag; + BitField value16bit; + BitField value15bit; + BitField signed16bit; +}; + +union TestUnion64 +{ + u64 bits; + BitField flag; + BitField value32bit; + BitField value31bit; + BitField 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(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(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(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(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(test8.value3bit), 3); + + // Test underflow handling + test8.value3bit = 0; + result = --test8.value3bit; + EXPECT_EQ(result, 7); // Should wrap around + EXPECT_EQ(static_cast(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(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(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(test8.value4bit), 8); + + // Test -= + test8.value4bit -= 2; + EXPECT_EQ(static_cast(test8.value4bit), 6); + + // Test *= + test8.value4bit *= 2; + EXPECT_EQ(static_cast(test8.value4bit), 12); + + // Test / = + test8.value4bit /= 3; + EXPECT_EQ(static_cast(test8.value4bit), 4); + + // Test overflow with += + test8.value4bit = 14; + test8.value4bit += 5; // Should overflow and wrap + EXPECT_EQ(static_cast(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(test8.value4bit), 8); + + // Test |= + test8.value4bit |= 3; // 1000 | 0011 = 1011 + EXPECT_EQ(static_cast(test8.value4bit), 11); + + // Test ^ = + test8.value4bit ^= 5; // 1011 ^ 0101 = 1110 + EXPECT_EQ(static_cast(test8.value4bit), 14); + + // Test << = + test8.value4bit = 3; // 0011 + test8.value4bit <<= 2; // 0011 << 2 = 1100 + EXPECT_EQ(static_cast(test8.value4bit), 12); + + // Test >> = + test8.value4bit >>= 1; // 1100 >> 1 = 0110 + EXPECT_EQ(static_cast(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(test8.flag)); + + test8.flag.SetValue(false); + EXPECT_FALSE(test8.flag.GetValue()); + EXPECT_FALSE(static_cast(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(test8.value3bit), 0); + + test8.value3bit.SetValue(7); + test8.value3bit *= 2; // 7 * 2 = 14, should wrap to 6 (14 & 0x7) + EXPECT_EQ(static_cast(test8.value3bit), 6); + + // Test underflow + test8.value3bit.SetValue(0); + test8.value3bit -= 1; // Should underflow + EXPECT_EQ(static_cast(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(test8.value3bit), 5); + + // Test chaining compound operators + auto& result2 = (test8.value3bit += 2); + EXPECT_EQ(&result2, &test8.value3bit); + EXPECT_EQ(static_cast(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 valid_bool; // This should compile + }; + + // This would fail to compile: + // BitField invalid_bool; // static_assert should trigger + + TestBoolField test{}; + test.bits = 0; + test.valid_bool = true; + EXPECT_TRUE(test.valid_bool); +} + template static inline constexpr unsigned ManualCountLeadingZeros(T value) { @@ -183,4 +595,275 @@ TEST(BitUtils, ByteSwap) EXPECT_EQ(ByteSwap(s16(0x1234)), s16(0x3412)); EXPECT_EQ(ByteSwap(s32(0x12345678)), s32(0x78563412)); EXPECT_EQ(ByteSwap(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(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(2047)), static_cast(1024)); + EXPECT_EQ(Common::PreviousPow2(static_cast(2048)), static_cast(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(ptr1), 16u)); + Common::AlignedFree(ptr1); + + void* ptr2 = Common::AlignedMalloc(256, 32); + ASSERT_NE(ptr2, nullptr); + EXPECT_TRUE(Common::IsAlignedPow2(reinterpret_cast(ptr2), 32u)); + Common::AlignedFree(ptr2); + + void* ptr3 = Common::AlignedMalloc(1024, 64); + ASSERT_NE(ptr3, nullptr); + EXPECT_TRUE(Common::IsAlignedPow2(reinterpret_cast(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(16, 10); + ASSERT_NE(ptr1.get(), nullptr); + EXPECT_TRUE(Common::IsAlignedPow2(reinterpret_cast(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(32, 100); + ASSERT_NE(ptr2.get(), nullptr); + EXPECT_TRUE(Common::IsAlignedPow2(reinterpret_cast(ptr2.get()), 32u)); + + // Test make_unique_aligned_for_overwrite + auto ptr3 = Common::make_unique_aligned_for_overwrite(64, 50); + ASSERT_NE(ptr3.get(), nullptr); + EXPECT_TRUE(Common::IsAlignedPow2(reinterpret_cast(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); + } } \ No newline at end of file diff --git a/src/common-tests/common-tests.vcxproj b/src/common-tests/common-tests.vcxproj index 125e17687..d88d87a72 100644 --- a/src/common-tests/common-tests.vcxproj +++ b/src/common-tests/common-tests.vcxproj @@ -5,9 +5,10 @@ + - + diff --git a/src/common-tests/common-tests.vcxproj.filters b/src/common-tests/common-tests.vcxproj.filters index ca02bfbf1..737de39e2 100644 --- a/src/common-tests/common-tests.vcxproj.filters +++ b/src/common-tests/common-tests.vcxproj.filters @@ -8,6 +8,7 @@ - + + \ No newline at end of file diff --git a/src/common-tests/gsvector_tests.cpp b/src/common-tests/gsvector_tests.cpp new file mode 100644 index 000000000..558b0a0e6 --- /dev/null +++ b/src/common-tests/gsvector_tests.cpp @@ -0,0 +1,1276 @@ +// SPDX-FileCopyrightText: 2019-2025 Connor McLaughlin +// SPDX-License-Identifier: CC-BY-NC-ND-4.0 + +#include "common/gsvector.h" + +#include "gtest/gtest.h" + +static constexpr s8 s8t(int v) +{ + return static_cast(static_cast(v)); +} + +static constexpr s16 s16t(int v) +{ + return static_cast(static_cast(v)); +} + +// GSVector2i Tests +TEST(GSVector2iTest, Construction) +{ + // Default constructor + [[maybe_unused]] GSVector2i v1; + // Values are uninitialized, so we don't test them + + // Single value constructor + GSVector2i v2(42); + EXPECT_EQ(v2.x, 42); + EXPECT_EQ(v2.y, 42); + + // Two value constructor + GSVector2i v3(10, 20); + EXPECT_EQ(v3.x, 10); + EXPECT_EQ(v3.y, 20); + + // 16-bit constructor + GSVector2i v4(s16(1), s16(2), s16(3), s16(4)); + EXPECT_EQ(v4.S16[0], 1); + EXPECT_EQ(v4.S16[1], 2); + EXPECT_EQ(v4.S16[2], 3); + EXPECT_EQ(v4.S16[3], 4); + + // 8-bit constructor + GSVector2i v5(s8t(1), s8t(2), s8t(3), s8t(4), s8t(5), s8t(6), s8t(7), s8t(8)); + for (int i = 0; i < 8; i++) + { + EXPECT_EQ(v5.S8[i], i + 1); + } + + // Copy constructor + GSVector2i v6(v3); + EXPECT_EQ(v6.x, 10); + EXPECT_EQ(v6.y, 20); +} + +TEST(GSVector2iTest, ConstexprCreation) +{ + constexpr auto v1 = GSVector2i::cxpr(5, 10); + EXPECT_EQ(v1.x, 5); + EXPECT_EQ(v1.y, 10); + + constexpr auto v2 = GSVector2i::cxpr(7); + EXPECT_EQ(v2.x, 7); + EXPECT_EQ(v2.y, 7); + + constexpr auto v3 = GSVector2i::cxpr16(s16(255)); + for (int i = 0; i < 4; i++) + { + EXPECT_EQ(v3.S16[i], 255); + } +} + +TEST(GSVector2iTest, SaturationOperations) +{ + GSVector2i v1(100, 200); + GSVector2i min_val(-50, -100); + GSVector2i max_val(150, 250); + + auto sat_result = v1.sat_s32(min_val, max_val); + EXPECT_EQ(sat_result.x, 100); // Within range + EXPECT_EQ(sat_result.y, 200); // Within range + + GSVector2i v2(300, -150); + auto sat_result2 = v2.sat_s32(min_val, max_val); + EXPECT_EQ(sat_result2.x, 150); // Clamped to max + EXPECT_EQ(sat_result2.y, -100); // Clamped to min +} + +TEST(GSVector2iTest, MinMaxVertical) +{ + GSVector2i v1(10, 20); + + EXPECT_EQ(v1.minv_s32(), 10); + EXPECT_EQ(v1.maxv_s32(), 20); + EXPECT_EQ(v1.addv_s32(), 30); + + GSVector2i v2(s8t(0x50), s8t(0x40), s8t(0x30), s8t(0x20), s8t(0x80), s8t(0x70), s8t(0x60), s8t(0x10)); // 8-bit values + EXPECT_EQ(v2.minv_u8(), 0x10u); + EXPECT_EQ(v2.maxv_u8(), 0x80u); + + GSVector2i v3(s16t(0x1000), s16t(0x2000), s16t(0x3000), s16t(0x4000)); // 16-bit values + EXPECT_EQ(v3.minv_u16(), 0x1000u); + EXPECT_EQ(v3.maxv_u16(), 0x4000u); +} + +TEST(GSVector2iTest, ClampOperations) +{ + // Test clamp8 which does pu16().upl8() + GSVector2i v1(300, 400, -100, 500); // Values that exceed 8-bit range + auto clamped = v1.clamp8(); + // This should pack to 16-bit unsigned with saturation, then unpack low 8-bit + for (int i = 0; i < 8; i++) + { + EXPECT_GE(clamped.U8[i], 0); + EXPECT_LE(clamped.U8[i], 255); + } +} + +TEST(GSVector2iTest, BlendOperations) +{ + GSVector2i v1(s8t(0x11), s8t(0x22), s8t(0x33), s8t(0x44), s8t(0x55), s8t(0x66), s8t(0x77), s8t(0x88)); + GSVector2i v2(s8t(0xAA), s8t(0xBB), s8t(0xCC), s8t(0xDD), s8t(0xEE), s8t(0xFF), s8t(0x00), s8t(0x11)); + GSVector2i mask(s8t(0x80), s8t(0x00), s8t(0x80), s8t(0x00), s8t(0x80), s8t(0x00), s8t(0x80), + s8t(0x00)); // Alternate selection + + auto blend_result = v1.blend8(v2, mask); + EXPECT_EQ(blend_result.U8[0], 0xAAu); // mask bit set, select from v2 + EXPECT_EQ(blend_result.U8[1], 0x22u); // mask bit clear, select from v1 + EXPECT_EQ(blend_result.U8[2], 0xCCu); // mask bit set, select from v2 + EXPECT_EQ(blend_result.U8[3], 0x44u); // mask bit clear, select from v1 +} + +TEST(GSVector2iTest, BlendTemplated) +{ + GSVector2i v1(s16t(0x1111), s16t(0x2222), s16t(0x3333), s16t(0x4444)); + GSVector2i v2(s16t(0xAAAA), s16t(0xBBBB), s16t(0xCCCC), s16t(0xDDDD)); + + // Test blend16 with mask 0x5 (binary 0101) - select elements 0 and 2 from v2 + auto blend16_result = v1.blend16<0x5>(v2); + EXPECT_EQ(blend16_result.U16[0], 0xAAAAu); // bit 0 set, select from v2 + EXPECT_EQ(blend16_result.U16[1], 0x2222u); // bit 1 clear, select from v1 + EXPECT_EQ(blend16_result.U16[2], 0xCCCCu); // bit 2 set, select from v2 + EXPECT_EQ(blend16_result.U16[3], 0x4444u); // bit 3 clear, select from v1 + + // Test blend32 with mask 0x1 - select element 0 from v2 + auto blend32_result = v1.blend32<0x1>(v2); + EXPECT_EQ(blend32_result.U32[0], 0xBBBBAAAAu); // bit 0 set, select from v2 + EXPECT_EQ(blend32_result.U32[1], 0x44443333u); // bit 1 clear, select from v1 +} + +TEST(GSVector2iTest, ShuffleOperations) +{ + GSVector2i v1(s8t(0x10), s8t(0x20), s8t(0x30), s8t(0x40), s8t(0x50), s8t(0x60), s8t(0x70), s8t(0x80)); + GSVector2i shuffle_mask(s8t(0x00), s8t(0x02), s8t(0x04), s8t(0x06), s8t(0x80), s8t(0x81), s8t(0x82), + s8t(0x83)); // Mix indices and zero + + auto shuffled = v1.shuffle8(shuffle_mask); + EXPECT_EQ(shuffled.S8[0], s8t(0x10)); // Index 0 + EXPECT_EQ(shuffled.S8[1], s8t(0x30)); // Index 2 + EXPECT_EQ(shuffled.S8[2], s8t(0x50)); // Index 4 + EXPECT_EQ(shuffled.S8[3], s8t(0x70)); // Index 6 + EXPECT_EQ(shuffled.S8[4], s8t(0)); // High bit set, zero + EXPECT_EQ(shuffled.S8[5], s8t(0)); // High bit set, zero +} + +TEST(GSVector2iTest, PackingOperations) +{ + // Test ps16 - pack signed 16-bit to signed 8-bit with saturation + GSVector2i v1(300, -200, 100, 400); // 16-bit values, some out of 8-bit range + auto packed_s = v1.ps16(); + EXPECT_EQ(packed_s.S8[0], 127); // 300 saturated to max s8 + EXPECT_EQ(packed_s.S8[1], -128); // -200 saturated to min s8 + EXPECT_EQ(packed_s.S8[2], 100); // 100 within range + EXPECT_EQ(packed_s.S8[3], 127); // 400 saturated to max s8 + + // Test pu16 - pack unsigned 16-bit to unsigned 8-bit with saturation + GSVector2i v2(100, 300, 50, 400); + auto packed_u = v2.pu16(); + EXPECT_EQ(packed_u.U8[0], 100); // 100 within range + EXPECT_EQ(packed_u.U8[1], 255); // 300 saturated to max u8 + EXPECT_EQ(packed_u.U8[2], 50); // 50 within range + EXPECT_EQ(packed_u.U8[3], 255); // 400 saturated to max u8 +} + +TEST(GSVector2iTest, UnpackOperations) +{ + GSVector2i v1(0x12, 0x34, 0x56, 0x78); + + auto upl8_result = v1.upl8(); + EXPECT_EQ(upl8_result.U8[0], 0x12); + EXPECT_EQ(upl8_result.U8[1], 0); + EXPECT_EQ(upl8_result.U8[2], 0); + EXPECT_EQ(upl8_result.U8[3], 0); + EXPECT_EQ(upl8_result.U8[4], 0x34); + EXPECT_EQ(upl8_result.U8[5], 0); + EXPECT_EQ(upl8_result.U8[6], 0); + EXPECT_EQ(upl8_result.U8[7], 0); + EXPECT_EQ(upl8_result.U8[8], 0x56); + EXPECT_EQ(upl8_result.U8[9], 0); + EXPECT_EQ(upl8_result.U8[10], 0); + EXPECT_EQ(upl8_result.U8[11], 0); + EXPECT_EQ(upl8_result.U8[12], 0x78); + EXPECT_EQ(upl8_result.U8[13], 0); + EXPECT_EQ(upl8_result.U8[14], 0); + EXPECT_EQ(upl8_result.U8[15], 0); + + auto upl16_result = v1.upl16(); + EXPECT_EQ(upl16_result.U16[0], 0x12); + EXPECT_EQ(upl16_result.U16[1], 0); + EXPECT_EQ(upl16_result.U16[2], 0x34); + EXPECT_EQ(upl16_result.U16[3], 0); + EXPECT_EQ(upl16_result.U16[4], 0x56); + EXPECT_EQ(upl16_result.U16[5], 0); + EXPECT_EQ(upl16_result.U16[6], 0x78); + EXPECT_EQ(upl16_result.U16[7], 0); +} + +TEST(GSVector2iTest, TypeConversions) +{ + GSVector2i v1(0x12, 0x34, 0x56, 0x78); + + // Test u8to16 + auto s8to16_result = v1.u8to16(); + EXPECT_EQ(s8to16_result.S16[0], 0x12); + EXPECT_EQ(s8to16_result.S16[1], 0); + EXPECT_EQ(s8to16_result.S16[2], 0x34); + EXPECT_EQ(s8to16_result.S16[3], 0); + + // Test u8to32 + auto u8to32_result = v1.u8to32(); + EXPECT_EQ(u8to32_result.U32[0], 0x12u); + EXPECT_EQ(u8to32_result.U32[1], 0u); +} + +TEST(GSVector2iTest, ByteShifts) +{ + GSVector2i v1(s8t(0x12), s8t(0x34), s8t(0x56), s8t(0x78), s8t(0x9A), s8t(0xBC), s8t(0xDE), s8t(0xF0)); + + // Test srl<2> - shift right logical by 2 bytes + auto srl_result = v1.srl<2>(); + EXPECT_EQ(srl_result.U8[0], 0x56u); + EXPECT_EQ(srl_result.U8[1], 0x78u); + EXPECT_EQ(srl_result.U8[2], 0x9Au); + EXPECT_EQ(srl_result.U8[3], 0xBCu); + EXPECT_EQ(srl_result.U8[4], 0xDEu); + EXPECT_EQ(srl_result.U8[5], 0xF0u); + EXPECT_EQ(srl_result.U8[6], 0u); + EXPECT_EQ(srl_result.U8[7], 0u); + + // Test sll<3> - shift left logical by 3 bytes + auto sll_result = v1.sll<3>(); + EXPECT_EQ(sll_result.U8[0], 0u); + EXPECT_EQ(sll_result.U8[1], 0u); + EXPECT_EQ(sll_result.U8[2], 0u); + EXPECT_EQ(sll_result.U8[3], 0x12u); + EXPECT_EQ(sll_result.U8[4], 0x34u); + EXPECT_EQ(sll_result.U8[5], 0x56u); + EXPECT_EQ(sll_result.U8[6], 0x78u); + EXPECT_EQ(sll_result.U8[7], 0x9Au); +} + +TEST(GSVector2iTest, ArithmeticWith16BitElements) +{ + GSVector2i v1(100, 200, 300, 400); + GSVector2i v2(50, 60, 70, 80); + + auto add16_result = v1.add16(v2); + EXPECT_EQ(add16_result.S16[0], 150); + EXPECT_EQ(add16_result.S16[1], 260); + EXPECT_EQ(add16_result.S16[2], 370); + EXPECT_EQ(add16_result.S16[3], 480); + + auto sub16_result = v1.sub16(v2); + EXPECT_EQ(sub16_result.S16[0], 50); + EXPECT_EQ(sub16_result.S16[1], 140); + EXPECT_EQ(sub16_result.S16[2], 230); + EXPECT_EQ(sub16_result.S16[3], 320); + + auto mul16_result = v1.mul16l(v2); + EXPECT_EQ(mul16_result.S16[0], 5000); + EXPECT_EQ(mul16_result.S16[1], 12000); + EXPECT_EQ(mul16_result.S16[2], 21000); + EXPECT_EQ(mul16_result.S16[3], 32000); +} + +TEST(GSVector2iTest, ArithmeticWith8BitElements) +{ + GSVector2i v1(10, 20, 30, 40, 50, 60, 70, 80); + GSVector2i v2(5, 8, 12, 16, 20, 24, 28, 32); + + auto add8_result = v1.add8(v2); + for (int i = 0; i < 8; i++) + { + EXPECT_EQ(add8_result.S8[i], v1.S8[i] + v2.S8[i]); + } + + auto sub8_result = v1.sub8(v2); + for (int i = 0; i < 8; i++) + { + EXPECT_EQ(sub8_result.S8[i], v1.S8[i] - v2.S8[i]); + } +} + +TEST(GSVector2iTest, SaturatedArithmetic) +{ + // Test signed saturation + GSVector2i v1(120, -120, 100, -100, 0, 0, 0, 0); + GSVector2i v2(50, -50, 60, -60, 0, 0, 0, 0); + + auto adds8_result = v1.adds8(v2); + EXPECT_EQ(adds8_result.S8[0], 127); // 120 + 50 = 170, saturated to 127 + EXPECT_EQ(adds8_result.S8[1], -128); // -120 + (-50) = -170, saturated to -128 + EXPECT_EQ(adds8_result.S8[2], 127); // 100 + 60 = 160, saturated to 127 + EXPECT_EQ(adds8_result.S8[3], -128); // -100 + (-60) = -160, saturated to -128 + + auto subs8_result = v1.subs8(v2); + EXPECT_EQ(subs8_result.S8[0], 70); // 120 - 50 = 70 + EXPECT_EQ(subs8_result.S8[1], -70); // -120 - (-50) = -70 + EXPECT_EQ(subs8_result.S8[2], 40); // 100 - 60 = 40 + EXPECT_EQ(subs8_result.S8[3], -40); // -100 - (-60) = -40 +} + +TEST(GSVector2iTest, UnsignedSaturatedArithmetic) +{ + GSVector2i v1(s8t(200), s8t(100), s8t(150), s8t(50), s8t(0), s8t(0), s8t(0), s8t(0)); + GSVector2i v2(s8t(80), s8t(120), s8t(30), s8t(70), s8t(0), s8t(0), s8t(0), s8t(0)); + + auto addus8_result = v1.addus8(v2); + EXPECT_EQ(addus8_result.U8[0], 255); // 200 + 80 = 280, saturated to 255 + EXPECT_EQ(addus8_result.U8[1], 220); // 100 + 120 = 220 + EXPECT_EQ(addus8_result.U8[2], 180); // 150 + 30 = 180 + EXPECT_EQ(addus8_result.U8[3], 120); // 50 + 70 = 120 + + auto subus8_result = v1.subus8(v2); + EXPECT_EQ(subus8_result.U8[0], 120); // 200 - 80 = 120 + EXPECT_EQ(subus8_result.U8[1], 0); // 100 - 120 = -20, saturated to 0 + EXPECT_EQ(subus8_result.U8[2], 120); // 150 - 30 = 120 + EXPECT_EQ(subus8_result.U8[3], 0); // 50 - 70 = -20, saturated to 0 +} + +TEST(GSVector2iTest, AverageOperations) +{ + GSVector2i v1(s8t(100), s8t(200), s8t(50), s8t(150), s8t(0), s8t(0), s8t(0), s8t(0)); + GSVector2i v2(s8t(80), s8t(180), s8t(70), s8t(130), s8t(0), s8t(0), s8t(0), s8t(0)); + + auto avg8_result = v1.avg8(v2); + EXPECT_EQ(avg8_result.U8[0], 90); // (100 + 80) / 2 = 90 + EXPECT_EQ(avg8_result.U8[1], 190); // (200 + 180) / 2 = 190 + EXPECT_EQ(avg8_result.U8[2], 60); // (50 + 70) / 2 = 60 + EXPECT_EQ(avg8_result.U8[3], 140); // (150 + 130) / 2 = 140 + + auto avg16_result = v1.avg16(v2); + EXPECT_EQ(avg16_result.U16[0], (51300 + 46160) / 2); // Average of packed 16-bit values + EXPECT_EQ(avg16_result.U16[1], (38450 + 33350) / 2); +} + +TEST(GSVector2iTest, ComparisonOperations) +{ + GSVector2i v1(10, 20, 30, 40, 50, 60, 70, 80); + GSVector2i v2(5, 25, 30, 45, 55, 55, 75, 75); + + // Test eq8 + auto eq8_result = v1.eq8(v2); + EXPECT_EQ(eq8_result.S8[0], 0); // 10 != 5 + EXPECT_EQ(eq8_result.S8[1], 0); // 20 != 25 + EXPECT_EQ(eq8_result.S8[2], -1); // 30 == 30 + EXPECT_EQ(eq8_result.S8[3], 0); // 40 != 45 + + // Test neq8 + auto neq8_result = v1.neq8(v2); + EXPECT_EQ(neq8_result.S8[0], -1); // 10 != 5 + EXPECT_EQ(neq8_result.S8[1], -1); // 20 != 25 + EXPECT_EQ(neq8_result.S8[2], 0); // 30 == 30 + EXPECT_EQ(neq8_result.S8[3], -1); // 40 != 45 + + // Test gt8 + auto gt8_result = v1.gt8(v2); + EXPECT_EQ(gt8_result.S8[0], -1); // 10 > 5 + EXPECT_EQ(gt8_result.S8[1], 0); // 20 < 25 + EXPECT_EQ(gt8_result.S8[2], 0); // 30 == 30 + EXPECT_EQ(gt8_result.S8[3], 0); // 40 < 45 + + // Test ge8 + auto ge8_result = v1.ge8(v2); + EXPECT_EQ(ge8_result.S8[0], -1); // 10 >= 5 + EXPECT_EQ(ge8_result.S8[1], 0); // 20 < 25 + EXPECT_EQ(ge8_result.S8[2], -1); // 30 >= 30 + EXPECT_EQ(ge8_result.S8[3], 0); // 40 < 45 + + // Test lt8 + auto lt8_result = v1.lt8(v2); + EXPECT_EQ(lt8_result.S8[0], 0); // 10 > 5 + EXPECT_EQ(lt8_result.S8[1], -1); // 20 < 25 + EXPECT_EQ(lt8_result.S8[2], 0); // 30 == 30 + EXPECT_EQ(lt8_result.S8[3], -1); // 40 < 45 + + // Test le8 + auto le8_result = v1.le8(v2); + EXPECT_EQ(le8_result.S8[0], 0); // 10 > 5 + EXPECT_EQ(le8_result.S8[1], -1); // 20 <= 25 + EXPECT_EQ(le8_result.S8[2], -1); // 30 <= 30 + EXPECT_EQ(le8_result.S8[3], -1); // 40 <= 45 +} + +TEST(GSVector2iTest, MaskAndBooleanOperations) +{ + GSVector2i v1(s8t(0x80), s8t(0x40), s8t(0x80), s8t(0x00), s8t(0x80), s8t(0x80), s8t(0x00), s8t(0x80)); + + s32 mask_result = v1.mask(); + // Mask should be formed from high bits of each byte + s32 expected_mask = 0x01 | 0x04 | 0x10 | 0x20 | 0x80; // Bits 0, 2, 4, 5, 7 + EXPECT_EQ(mask_result & 0xB5, expected_mask & 0xB5); // Check set bits + + // Test alltrue and allfalse + GSVector2i all_ones; + all_ones.U64[0] = 0xFFFFFFFFFFFFFFFFULL; + EXPECT_TRUE(all_ones.alltrue()); + EXPECT_FALSE(all_ones.allfalse()); + + GSVector2i all_zeros; + all_zeros.U64[0] = 0; + EXPECT_FALSE(all_zeros.alltrue()); + EXPECT_TRUE(all_zeros.allfalse()); +} + +TEST(GSVector2iTest, InsertExtractOperations) +{ + GSVector2i v1(0x12345678, 0x9ABCDEF0); + + // Test insert/extract 8-bit + auto v_insert8 = v1.insert8<0>(0x55); + EXPECT_EQ(v_insert8.extract8<0>(), 0x55); + EXPECT_EQ(v1.extract8<1>(), s8t(0x56)); + + // Test insert/extract 16-bit + auto v_insert16 = v1.insert16<1>(0x1234); + EXPECT_EQ(v_insert16.extract16<1>(), 0x1234); + EXPECT_EQ(v1.extract16<0>(), static_cast(0x5678)); + + // Test insert/extract 32-bit + auto v_insert32 = v1.insert32<0>(0xAABBCCDD); + EXPECT_EQ(v_insert32.extract32<0>(), static_cast(0xAABBCCDD)); + EXPECT_EQ(v1.extract32<1>(), static_cast(0x9ABCDEF0)); +} + +TEST(GSVector2iTest, LoadStoreOperations) +{ + // Test load32 + s32 value = 0x12345678; + auto loaded32 = GSVector2i::load32(&value); + EXPECT_EQ(loaded32.x, 0x12345678); + EXPECT_EQ(loaded32.y, 0); + + // Test set32 + auto set32_result = GSVector2i::set32(0xAABBCCDD); + EXPECT_EQ(set32_result.x, static_cast(0xAABBCCDD)); + EXPECT_EQ(set32_result.y, 0); + + // Test store32 + s32 output_value; + GSVector2i::store32(&output_value, loaded32); + EXPECT_EQ(output_value, 0x12345678); + + // Test full load/store + s32 data[2] = {0x11111111, 0x22222222}; + auto loaded = GSVector2i::load(data); + EXPECT_EQ(loaded.S32[0], 0x11111111); + EXPECT_EQ(loaded.S32[1], 0x22222222); + + s32 output[2]; + GSVector2i::store(output, loaded); + EXPECT_EQ(output[0], 0x11111111); + EXPECT_EQ(output[1], 0x22222222); +} + +TEST(GSVector2iTest, BitwiseAssignmentOperations) +{ + GSVector2i v1(0xF0F0F0F0, 0x0F0F0F0F); + GSVector2i v2(0xAAAAAAAA, 0x55555555); + + // Test &= + GSVector2i v_and = v1; + v_and &= v2; + EXPECT_EQ(v_and.U32[0], 0xA0A0A0A0u); + EXPECT_EQ(v_and.U32[1], 0x05050505u); + + // Test |= + GSVector2i v_or = v1; + v_or |= v2; + EXPECT_EQ(v_or.U32[0], 0xFAFAFAFAu); + EXPECT_EQ(v_or.U32[1], 0x5F5F5F5Fu); + + // Test ^= + GSVector2i v_xor = v1; + v_xor ^= v2; + EXPECT_EQ(v_xor.U32[0], 0x5A5A5A5Au); + EXPECT_EQ(v_xor.U32[1], 0x5A5A5A5Au); +} + +TEST(GSVector2iTest, BitwiseScalarOperations) +{ + GSVector2i v1(0xF0F0F0F0, 0x0F0F0F0F); + s32 scalar = 0xAAAAAAAA; + + auto and_result = v1 & scalar; + EXPECT_EQ(and_result.U32[0], 0xA0A0A0A0u); + EXPECT_EQ(and_result.U32[1], 0x0A0A0A0Au); + + auto or_result = v1 | scalar; + EXPECT_EQ(or_result.U32[0], 0xFAFAFAFAu); + EXPECT_EQ(or_result.U32[1], 0xAFAFAFAFu); + + auto xor_result = v1 ^ scalar; + EXPECT_EQ(xor_result.U32[0], 0x5A5A5A5Au); + EXPECT_EQ(xor_result.U32[1], 0xA5A5A5A5u); +} + +TEST(GSVector2iTest, NotOperation) +{ + GSVector2i v1(0xF0F0F0F0, 0x0F0F0F0F); + auto not_result = ~v1; + EXPECT_EQ(not_result.U32[0], 0x0F0F0F0Fu); + EXPECT_EQ(not_result.U32[1], 0xF0F0F0F0u); +} + +// GSVector2 Tests +TEST(GSVector2Test, Construction) +{ + // Single value constructor + GSVector2 v1(3.14f); + EXPECT_FLOAT_EQ(v1.x, 3.14f); + EXPECT_FLOAT_EQ(v1.y, 3.14f); + + // Two value constructor (float) + GSVector2 v2(1.5f, 2.5f); + EXPECT_FLOAT_EQ(v2.x, 1.5f); + EXPECT_FLOAT_EQ(v2.y, 2.5f); + + // Two value constructor (int) + GSVector2 v3(10, 20); + EXPECT_FLOAT_EQ(v3.x, 10.0f); + EXPECT_FLOAT_EQ(v3.y, 20.0f); + + // Single int constructor + GSVector2 v4(42); + EXPECT_FLOAT_EQ(v4.x, 42.0f); + EXPECT_FLOAT_EQ(v4.y, 42.0f); +} + +TEST(GSVector2Test, BlendOperations) +{ + GSVector2 v1(1.0f, 2.0f); + GSVector2 v2(3.0f, 4.0f); + + // Test templated blend32 + auto blend_result = v1.blend32<1>(v2); // mask = 1, select x from v1, y from v2 + EXPECT_FLOAT_EQ(blend_result.x, 3.0f); // From v2 + EXPECT_FLOAT_EQ(blend_result.y, 2.0f); // From v1 + + // Test mask-based blend32 + GSVector2 mask; + mask.U32[0] = 0x80000000u; // High bit set + mask.U32[1] = 0x00000000u; // High bit clear + auto mask_blend_result = v1.blend32(v2, mask); + EXPECT_FLOAT_EQ(mask_blend_result.x, 3.0f); // From v2 (mask high bit set) + EXPECT_FLOAT_EQ(mask_blend_result.y, 2.0f); // From v1 (mask high bit clear) +} + +TEST(GSVector2Test, MaskOperations) +{ + GSVector2 v1; + v1.U32[0] = 0x80000000u; // High bit set + v1.U32[1] = 0x40000000u; // Second bit set + + int mask_result = v1.mask(); + EXPECT_EQ(mask_result, 0x1); // One bit should be set in result +} + +TEST(GSVector2Test, ReplaceNaN) +{ + GSVector2 v1(1.0f, std::numeric_limits::quiet_NaN()); + GSVector2 replacement(99.0f, 88.0f); + + auto result = v1.replace_nan(replacement); + EXPECT_FLOAT_EQ(result.x, 1.0f); // Not NaN, keep original + EXPECT_FLOAT_EQ(result.y, 88.0f); // Was NaN, use replacement +} + +TEST(GSVector2Test, InsertExtract) +{ + GSVector2 v1(1.0f, 2.0f); + GSVector2 v2(3.0f, 4.0f); + + // Test insert32 + auto insert_result = v1.insert32<1, 0>(v2); // Insert v2[1] into v1[0] + EXPECT_FLOAT_EQ(insert_result.x, 4.0f); // v2[1] + EXPECT_FLOAT_EQ(insert_result.y, 2.0f); // Original v1[1] + + // Test extract32 + GSVector2 v3; + v3.I32[0] = 0x12345678; + v3.I32[1] = 0x9ABCDEF0; + EXPECT_EQ(v3.extract32<0>(), 0x12345678); + EXPECT_EQ(v3.extract32<1>(), static_cast(0x9ABCDEF0)); +} + +TEST(GSVector2Test, ComparisonOperators) +{ + GSVector2 v1(1.0f, 2.0f); + GSVector2 v2(1.0f, 3.0f); + + auto eq_result = v1 == v2; + EXPECT_EQ(eq_result.I32[0], -1); // 1.0 == 1.0 + EXPECT_EQ(eq_result.I32[1], 0); // 2.0 != 3.0 + + auto neq_result = v1 != v2; + EXPECT_EQ(neq_result.I32[0], 0); // 1.0 == 1.0 + EXPECT_EQ(neq_result.I32[1], -1); // 2.0 != 3.0 + + auto gt_result = v1 > v2; + EXPECT_EQ(gt_result.I32[0], 0); // 1.0 == 1.0 + EXPECT_EQ(gt_result.I32[1], 0); // 2.0 < 3.0 + + auto lt_result = v1 < v2; + EXPECT_EQ(lt_result.I32[0], 0); // 1.0 == 1.0 + EXPECT_EQ(lt_result.I32[1], -1); // 2.0 < 3.0 + + auto ge_result = v1 >= v2; + EXPECT_EQ(ge_result.I32[0], -1); // 1.0 >= 1.0 + EXPECT_EQ(ge_result.I32[1], 0); // 2.0 < 3.0 + + auto le_result = v1 <= v2; + EXPECT_EQ(le_result.I32[0], -1); // 1.0 <= 1.0 + EXPECT_EQ(le_result.I32[1], -1); // 2.0 <= 3.0 +} + +TEST(GSVector2Test, XfffffffffConstant) +{ + const auto all_ones = GSVector2::xffffffff(); + EXPECT_EQ(all_ones.U64[0], 0xFFFFFFFFFFFFFFFFULL); +} + +// GSVector4i Tests +TEST(GSVector4iTest, ConstexprCreation8Bit) +{ + constexpr auto v1 = GSVector4i::cxpr8(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16); + for (int i = 0; i < 16; i++) + { + EXPECT_EQ(v1.S8[i], i + 1); + } +} + +TEST(GSVector4iTest, MaddOperations) +{ + GSVector4i v1(1, 2, 3, 4, 5, 6, 7, 8); // 16-bit values + GSVector4i v2(2, 3, 4, 5, 6, 7, 8, 9); + + auto madd_result = v1.madd_s16(v2); + EXPECT_EQ(madd_result.S32[0], 1 * 2 + 2 * 3); // 2 + 6 = 8 + EXPECT_EQ(madd_result.S32[1], 3 * 4 + 4 * 5); // 12 + 20 = 32 + EXPECT_EQ(madd_result.S32[2], 5 * 6 + 6 * 7); // 30 + 42 = 72 + EXPECT_EQ(madd_result.S32[3], 7 * 8 + 8 * 9); // 56 + 72 = 128 +} + +TEST(GSVector4iTest, HorizontalAdd) +{ + GSVector4i v1(10, 20, 30, 40); + + auto addp_result = v1.addp_s32(); + EXPECT_EQ(addp_result.x, 30); // 10 + 20 + EXPECT_EQ(addp_result.y, 70); // 30 + 40 + EXPECT_EQ(addp_result.z, 30); + EXPECT_EQ(addp_result.w, 70); +} + +TEST(GSVector4iTest, VerticalMinMaxU8) +{ + GSVector4i v1(s8t(10), s8t(50), s8t(30), s8t(200), s8t(15), s8t(100), s8t(5), s8t(250), s8t(80), s8t(20), s8t(60), + s8t(40), s8t(90), s8t(70), s8t(180), s8t(1)); + + EXPECT_EQ(v1.minv_u8(), 1u); + EXPECT_EQ(v1.maxv_u8(), 250u); +} + +TEST(GSVector4iTest, VerticalMinMaxU16) +{ + GSVector4i v1(1000, 2000, 500, 3000, 1500, 800, 2500, 100); + + EXPECT_EQ(v1.minv_u16(), 100u); + EXPECT_EQ(v1.maxv_u16(), 3000u); +} + +TEST(GSVector4iTest, HaddS16) +{ + GSVector4i v1(10, 20, 30, 40, 50, 60, 70, 80); + GSVector4i v2(1, 2, 3, 4, 5, 6, 7, 8); + + auto hadds_result = v1.hadds16(v2); + // First vector: pairs (10,20), (30,40), (50,60), (70,80) + // Second vector: pairs (1,2), (3,4), (5,6), (7,8) + EXPECT_EQ(hadds_result.S16[0], 30); // 10 + 20 + EXPECT_EQ(hadds_result.S16[1], 70); // 30 + 40 + EXPECT_EQ(hadds_result.S16[2], 110); // 50 + 60 + EXPECT_EQ(hadds_result.S16[3], 150); // 70 + 80 + EXPECT_EQ(hadds_result.S16[4], 3); // 1 + 2 + EXPECT_EQ(hadds_result.S16[5], 7); // 3 + 4 + EXPECT_EQ(hadds_result.S16[6], 11); // 5 + 6 + EXPECT_EQ(hadds_result.S16[7], 15); // 7 + 8 +} + +TEST(GSVector4iTest, BlendOperationsAdvanced) +{ + GSVector4i v1(s8t(0x11), s8t(0x22), s8t(0x33), s8t(0x44), s8t(0x55), s8t(0x66), s8t(0x77), s8t(0x88), s8t(0x99), + s8t(0xAA), s8t(0xBB), s8t(0xCC), s8t(0xDD), s8t(0xEE), s8t(0xFF), s8t(0x00)); + GSVector4i v2(s8t(0xA1), s8t(0xB2), s8t(0xC3), s8t(0xD4), s8t(0xE5), s8t(0xF6), s8t(0x07), s8t(0x18), s8t(0x29), + s8t(0x3A), s8t(0x4B), s8t(0x5C), s8t(0x6D), s8t(0x7E), s8t(0x8F), s8t(0x90)); + GSVector4i mask_blend(s8t(0xFF), s8t(0x00), s8t(0xFF), s8t(0x00), s8t(0xFF), s8t(0x00), s8t(0xFF), s8t(0x00), + s8t(0xFF), s8t(0x00), s8t(0xFF), s8t(0x00), s8t(0xFF), s8t(0x00), s8t(0xFF), s8t(0x00)); + + auto blend_result = v1.blend(v2, mask_blend); + // The blend operation should mix bits based on the mask + // Where mask bit is 1, select from v2; where 0, select from v1 + for (int i = 0; i < 2; i++) + { + u64 expected = (v2.U64[i] & mask_blend.U64[i]) | (v1.U64[i] & ~mask_blend.U64[i]); + EXPECT_EQ(blend_result.U64[i], expected); + } +} + +TEST(GSVector4iTest, AdvancedPackingWithTwoVectors) +{ + GSVector4i v1(100, 200, 300, 400); // 32-bit signed values + GSVector4i v2(500, 600, 700, 800); + + auto ps32_result = v1.ps32(v2); + // Should pack both vectors' 32-bit values to 16-bit with saturation + EXPECT_EQ(ps32_result.S16[0], 100); + EXPECT_EQ(ps32_result.S16[1], 200); + EXPECT_EQ(ps32_result.S16[2], 300); + EXPECT_EQ(ps32_result.S16[3], 400); + EXPECT_EQ(ps32_result.S16[4], 500); + EXPECT_EQ(ps32_result.S16[5], 600); + EXPECT_EQ(ps32_result.S16[6], 700); + EXPECT_EQ(ps32_result.S16[7], 800); +} + +TEST(GSVector4iTest, AdvancedUnpackingWithTwoVectors) +{ + GSVector4i v1(s8t(0x11), s8t(0x22), s8t(0x33), s8t(0x44), s8t(0x55), s8t(0x66), s8t(0x77), s8t(0x88), s8t(0x99), + s8t(0xAA), s8t(0xBB), s8t(0xCC), s8t(0xDD), s8t(0xEE), s8t(0xFF), s8t(0x00)); + GSVector4i v2(s8t(0xA1), s8t(0xB2), s8t(0xC3), s8t(0xD4), s8t(0xE5), s8t(0xF6), s8t(0x07), s8t(0x18), s8t(0x29), + s8t(0x3A), s8t(0x4B), s8t(0x5C), s8t(0x6D), s8t(0x7E), s8t(0x8F), s8t(0x90)); + + auto upl8_result = v1.upl8(v2); + // Should interleave low 8 bytes from both vectors + EXPECT_EQ(upl8_result.S8[0], s8t(0x11)); // v1[0] + EXPECT_EQ(upl8_result.S8[1], s8t(0xA1)); // v2[0] + EXPECT_EQ(upl8_result.S8[2], s8t(0x22)); // v1[1] + EXPECT_EQ(upl8_result.S8[3], s8t(0xB2)); // v2[1] + + auto uph8_result = v1.uph8(v2); + // Should interleave high 8 bytes from both vectors + EXPECT_EQ(uph8_result.S8[0], s8t(0x99)); // v1[8] + EXPECT_EQ(uph8_result.S8[1], s8t(0x29)); // v2[8] + EXPECT_EQ(uph8_result.S8[2], s8t(0xAA)); // v1[9] + EXPECT_EQ(uph8_result.S8[3], s8t(0x3A)); // v2[9] +} + +TEST(GSVector4iTest, Type64BitConversions) +{ + GSVector4i v1(s8t(0x12), s8t(0x34), s8t(0x56), s8t(0x78), s8t(0x9A), s8t(0xBC), s8t(0xDE), s8t(0xF0), s8t(0x11), + s8t(0x22), s8t(0x33), s8t(0x44), s8t(0x55), s8t(0x66), s8t(0x77), s8t(0x88)); + + // Test s8to64 + auto s8to64_result = v1.s8to64(); + EXPECT_EQ(s8to64_result.S64[0], 0x12); + EXPECT_EQ(s8to64_result.S64[1], 0x34); + + // Test u16to64 + auto u16to64_result = v1.u16to64(); + EXPECT_EQ(u16to64_result.U64[0], 0x3412u); // Little endian 16-bit + EXPECT_EQ(u16to64_result.U64[1], 0x7856u); + + // Test s32to64 + auto s32to64_result = v1.s32to64(); + EXPECT_EQ(s32to64_result.S64[0], static_cast(0x0000000078563412LL)); + EXPECT_EQ(s32to64_result.S64[1], static_cast(0xFFFFFFFFF0DEBC9ALL)); +} + +TEST(GSVector4iTest, Shift64BitOperations) +{ + GSVector4i v1; + v1.U64[0] = 0x123456789ABCDEF0ULL; + v1.U64[1] = 0xFEDCBA0987654321ULL; + + // Test sll64 + auto sll64_result = v1.sll64<4>(); + EXPECT_EQ(sll64_result.U64[0], 0x23456789ABCDEF00ULL); + EXPECT_EQ(sll64_result.U64[1], 0xEDCBA09876543210ULL); + + // Test srl64 + auto srl64_result = v1.srl64<4>(); + EXPECT_EQ(srl64_result.U64[0], 0x0123456789ABCDEFULL); + EXPECT_EQ(srl64_result.U64[1], 0x0FEDCBA098765432ULL); +} + +#ifdef GSVECTOR_HAS_SRLV +TEST(GSVector4iTest, VariableShifts) +{ + GSVector4i v1(0x1000, 0x2000, 0x4000, 0x8000); + GSVector4i shift_amounts(1, 2, 3, 4); + + auto sllv16_result = v1.sllv16(shift_amounts); + EXPECT_EQ(sllv16_result.U16[0], 0x2000); // 0x1000 << 1 + EXPECT_EQ(sllv16_result.U16[1], 0x8000); // 0x2000 << 2 + EXPECT_EQ(sllv16_result.U16[2], 0x0000); // 0x4000 << 3 (overflow) + EXPECT_EQ(sllv16_result.U16[3], 0x0000); // 0x8000 << 4 (overflow) + + auto srlv16_result = v1.srlv16(shift_amounts); + EXPECT_EQ(srlv16_result.U16[0], 0x0800); // 0x1000 >> 1 + EXPECT_EQ(srlv16_result.U16[1], 0x0800); // 0x2000 >> 2 + EXPECT_EQ(srlv16_result.U16[2], 0x0800); // 0x4000 >> 3 + EXPECT_EQ(srlv16_result.U16[3], 0x0800); // 0x8000 >> 4 +} +#endif + +TEST(GSVector4iTest, MultiplicationOperations) +{ + GSVector4i v1(10, 20, 30, 40, 50, 60, 70, 80); + GSVector4i v2(2, 3, 4, 5, 6, 7, 8, 9); + + // Test mul16hs - high 16 bits of 16-bit multiplication + auto mul16hs_result = v1.mul16hs(v2); + // For 16-bit values, this should mostly be 0 unless we have large values + for (int i = 0; i < 8; i++) + { + s32 expected = (v1.S16[i] * v2.S16[i]) >> 16; + EXPECT_EQ(mul16hs_result.S16[i], expected); + } + + // Test mul16hrs - rounded high 16 bits + auto mul16hrs_result = v1.mul16hrs(v2); + for (int i = 0; i < 8; i++) + { + const s16 expected = static_cast((((v1.S16[i] * v2.S16[i]) >> 14) + 1) >> 1); + EXPECT_EQ(mul16hrs_result.S16[i], expected); + } +} + +TEST(GSVector4iTest, Eq64Operations) +{ + GSVector4i v1; + GSVector4i v2; + v1.S64[0] = 0x123456789ABCDEF0LL; + v1.S64[1] = 0xFEDCBA0987654321LL; + v2.S64[0] = 0x123456789ABCDEF0LL; // Same as v1[0] + v2.S64[1] = 0x1111111111111111LL; // Different from v1[1] + + auto eq64_result = v1.eq64(v2); + EXPECT_EQ(eq64_result.S64[0], -1); // Equal + EXPECT_EQ(eq64_result.S64[1], 0); // Not equal +} + +TEST(GSVector4iTest, InsertExtract64Bit) +{ + GSVector4i v1(0x12345678, 0x9ABCDEF0, 0x11111111, 0x22222222); + + // Test insert64 + auto v_insert64 = v1.insert64<0>(static_cast(0x9999888877776666ULL)); + EXPECT_EQ(v_insert64.extract64<0>(), static_cast(0x9999888877776666ULL)); + EXPECT_EQ(v_insert64.extract64<1>(), v1.extract64<1>()); + + // Test extract64 + EXPECT_EQ(v1.extract64<0>(), static_cast(0x9ABCDEF012345678ULL)); // Little endian combination + EXPECT_EQ(v1.extract64<1>(), static_cast(0x2222222211111111ULL)); +} + +TEST(GSVector4iTest, LoadStoreSpecialOperations) +{ + // Test loadnt (non-temporal load) + alignas(VECTOR_ALIGNMENT) static constexpr const s32 data[4] = {0x11111111, 0x22222222, 0x33333333, 0x44444444}; + auto loaded_nt = GSVector4i::loadnt(data); + for (int i = 0; i < 4; i++) + { + EXPECT_EQ(loaded_nt.S32[i], data[i]); + } + + // Test storent (non-temporal store) + alignas(VECTOR_ALIGNMENT) s32 output_nt[4]; + GSVector4i::storent(output_nt, loaded_nt); + for (int i = 0; i < 4; i++) + { + EXPECT_EQ(output_nt[i], data[i]); + } + + // Test zext32 + auto zext_result = GSVector4i::zext32(0x12345678); + EXPECT_EQ(zext_result.x, 0x12345678); + EXPECT_EQ(zext_result.y, 0); + EXPECT_EQ(zext_result.z, 0); + EXPECT_EQ(zext_result.w, 0); +} + +TEST(GSVector4iTest, LoadStoreHalfOperations) +{ + // Test loadl and loadh + u64 data[2] = {0x123456789ABCDEF0ULL, 0xFEDCBA0987654321ULL}; + + auto loaded_low = GSVector4i::loadl(data); + EXPECT_EQ(loaded_low.U64[0], data[0]); + EXPECT_EQ(loaded_low.U64[1], 0u); + + auto loaded_high = GSVector4i::loadh(data); + EXPECT_EQ(loaded_high.U64[0], 0u); + EXPECT_EQ(loaded_high.U64[1], data[0]); + + // Test storel and storeh + GSVector4i test_vec; + test_vec.U64[0] = 0xAAAABBBBCCCCDDDDULL; + test_vec.U64[1] = 0xEEEEFFFF00001111ULL; + + s32 output_low[2]; + GSVector4i::storel(output_low, test_vec); + EXPECT_EQ(reinterpret_cast(output_low)[0], test_vec.U64[0]); + + s32 output_high[2]; + GSVector4i::storeh(output_high, test_vec); + EXPECT_EQ(reinterpret_cast(output_high)[0], test_vec.U64[1]); +} + +TEST(GSVector4iTest, BroadcastOperations) +{ + GSVector4i v1(10, 20, 30, 40); + + auto broadcast_result = GSVector4i::broadcast128(v1); + // In no-SIMD implementation, this just returns the same vector + EXPECT_EQ(broadcast_result.x, v1.x); + EXPECT_EQ(broadcast_result.y, v1.y); + EXPECT_EQ(broadcast_result.z, v1.z); + EXPECT_EQ(broadcast_result.w, v1.w); +} + +TEST(GSVector4iTest, StaticHelperFunctions) +{ + GSVector2i xy(10, 20); + GSVector2i zw(30, 40); + + auto xyxy_result1 = GSVector4i::xyxy(xy, zw); + EXPECT_EQ(xyxy_result1.x, 10); + EXPECT_EQ(xyxy_result1.y, 20); + EXPECT_EQ(xyxy_result1.z, 30); + EXPECT_EQ(xyxy_result1.w, 40); + + auto xyxy_result2 = GSVector4i::xyxy(xy); + EXPECT_EQ(xyxy_result2.x, 10); + EXPECT_EQ(xyxy_result2.y, 20); + EXPECT_EQ(xyxy_result2.z, 10); + EXPECT_EQ(xyxy_result2.w, 20); +} + +// GSVector4 Tests +TEST(GSVector4Test, DoubleOperations) +{ + // Test all 64-bit double operations + GSVector4 v1 = GSVector4::f64(3.14159, 2.71828); + GSVector4 v2 = GSVector4::f64(1.41421, 1.73205); + + auto add64_result = v1.add64(v2); + EXPECT_DOUBLE_EQ(add64_result.F64[0], 3.14159 + 1.41421); + EXPECT_DOUBLE_EQ(add64_result.F64[1], 2.71828 + 1.73205); + + auto sub64_result = v1.sub64(v2); + EXPECT_DOUBLE_EQ(sub64_result.F64[0], 3.14159 - 1.41421); + EXPECT_DOUBLE_EQ(sub64_result.F64[1], 2.71828 - 1.73205); + + auto mul64_result = v1.mul64(v2); + EXPECT_DOUBLE_EQ(mul64_result.F64[0], 3.14159 * 1.41421); + EXPECT_DOUBLE_EQ(mul64_result.F64[1], 2.71828 * 1.73205); + + auto div64_result = v1.div64(v2); + EXPECT_DOUBLE_EQ(div64_result.F64[0], 3.14159 / 1.41421); + EXPECT_DOUBLE_EQ(div64_result.F64[1], 2.71828 / 1.73205); +} + +TEST(GSVector4Test, BasicOps) +{ + GSVector4 v(1.0f, -2.0f, 3.5f, -4.5f); + + EXPECT_FLOAT_EQ(v.addv(), (1.0f - 2.0f + 3.5f - 4.5f)); + EXPECT_FLOAT_EQ(v.minv(), -4.5f); + EXPECT_FLOAT_EQ(v.maxv(), 3.5f); + + auto av = v.abs(); + EXPECT_FLOAT_EQ(av.x, 1.0f); + EXPECT_FLOAT_EQ(av.y, 2.0f); + EXPECT_FLOAT_EQ(av.z, 3.5f); + EXPECT_FLOAT_EQ(av.w, 4.5f); + + auto nv = v.neg(); + EXPECT_FLOAT_EQ(nv.x, -1.0f); + EXPECT_FLOAT_EQ(nv.y, 2.0f); + EXPECT_FLOAT_EQ(nv.z, -3.5f); + EXPECT_FLOAT_EQ(nv.w, 4.5f); + + auto fl = GSVector4(1.9f, -1.2f, 3.01f, -3.99f).floor(); + EXPECT_FLOAT_EQ(fl.x, 1.0f); + EXPECT_FLOAT_EQ(fl.y, -2.0f); + EXPECT_FLOAT_EQ(fl.z, 3.0f); + EXPECT_FLOAT_EQ(fl.w, -4.0f); + + auto cl = GSVector4(1.1f, -1.2f, 3.01f, -3.99f).ceil(); + EXPECT_FLOAT_EQ(cl.x, 2.0f); + EXPECT_FLOAT_EQ(cl.y, -1.0f); + EXPECT_FLOAT_EQ(cl.z, 4.0f); + EXPECT_FLOAT_EQ(cl.w, -3.0f); + + // sat(scale) + auto sat_scaled = GSVector4(-5.0f, 10.0f, 500.0f, 260.0f).sat(255.0f); + EXPECT_FLOAT_EQ(sat_scaled.x, 0.0f); + EXPECT_FLOAT_EQ(sat_scaled.y, 10.0f); + EXPECT_FLOAT_EQ(sat_scaled.z, 255.0f); + EXPECT_FLOAT_EQ(sat_scaled.w, 255.0f); + + // sat(minmax vector) : x/z clamped to [min.x, min.z], y/w to [min.y, min.w] + GSVector4 range(0.0f, -1.0f, 2.0f, 1.0f); + auto sat_pair = v.sat(range); + EXPECT_FLOAT_EQ(sat_pair.x, 1.0f); // within [0,2] + EXPECT_FLOAT_EQ(sat_pair.y, -1.0f); // clamped to -1 + EXPECT_FLOAT_EQ(sat_pair.z, 2.0f); // clamped to 2 + EXPECT_FLOAT_EQ(sat_pair.w, -1.0f); // clamped to -1 +} + +TEST(GSVector4Test, BlendAndMask) +{ + GSVector4 a(1, 2, 3, 4); + GSVector4 b(5, 6, 7, 8); + + // Template blend32 (selects only lanes 0/1 from the 'v' argument per bit) + auto tb = a.blend32<0b1010>(b); + EXPECT_FLOAT_EQ(tb.x, 1.0f); // bit0 = 0 -> v[0] + EXPECT_FLOAT_EQ(tb.y, 6.0f); // bit1 = 1 -> v[1] + EXPECT_FLOAT_EQ(tb.z, 3.0f); // bit2 = 0 -> v[0] + EXPECT_FLOAT_EQ(tb.w, 8.0f); // bit3 = 1 -> v[1] + + // Masked blend: high bit set -> take from second vector argument (b); else from 'a' + GSVector4 mask; + mask.U32[0] = 0x00000000u; + mask.U32[1] = 0x80000000u; + mask.U32[2] = 0x00000000u; + mask.U32[3] = 0x80000000u; + auto mb = a.blend32(b, mask); + EXPECT_FLOAT_EQ(mb.x, a.x); + EXPECT_FLOAT_EQ(mb.y, b.y); + EXPECT_FLOAT_EQ(mb.z, a.z); + EXPECT_FLOAT_EQ(mb.w, b.w); + + // mask() bit packing (bits 31,23,15,7) + GSVector4 m; + m.U32[0] = 0x80000000u; // sets bit 0 + m.U32[1] = 0x40000000u; // sets bit 1 + m.U32[2] = 0x20000000u; // sets bit 2 + m.U32[3] = 0x10000000u; // sets bit 3 + EXPECT_EQ(m.mask(), 0x1); +} + +TEST(GSVector4Test, HorizontalAndInterleave) +{ + GSVector4 v(1, 2, 10, 20); + auto hadd0 = v.hadd(); + EXPECT_FLOAT_EQ(hadd0.x, 3); + EXPECT_FLOAT_EQ(hadd0.y, 30); + EXPECT_FLOAT_EQ(hadd0.z, 3); + EXPECT_FLOAT_EQ(hadd0.w, 30); + + auto hsub0 = v.hsub(); + EXPECT_FLOAT_EQ(hsub0.x, -1); + EXPECT_FLOAT_EQ(hsub0.y, -10); + EXPECT_FLOAT_EQ(hsub0.z, -1); + EXPECT_FLOAT_EQ(hsub0.w, -10); + + GSVector4 v2(3, 4, 5, 6); + auto hadd1 = v.hadd(v2); + EXPECT_FLOAT_EQ(hadd1.x, 3); + EXPECT_FLOAT_EQ(hadd1.y, 30); + EXPECT_FLOAT_EQ(hadd1.z, 7); + EXPECT_FLOAT_EQ(hadd1.w, 11); + + auto hsub1 = v.hsub(v2); + EXPECT_FLOAT_EQ(hsub1.x, -1); + EXPECT_FLOAT_EQ(hsub1.y, -10); + EXPECT_FLOAT_EQ(hsub1.z, -1); + EXPECT_FLOAT_EQ(hsub1.w, -1); + + // Interleave / low-high helpers + GSVector4 a(1, 2, 3, 4); + GSVector4 b(5, 6, 7, 8); + auto upl = a.upl(b); + EXPECT_FLOAT_EQ(upl.x, 1); + EXPECT_FLOAT_EQ(upl.y, 5); + EXPECT_FLOAT_EQ(upl.z, 2); + EXPECT_FLOAT_EQ(upl.w, 6); + auto uph = a.uph(b); + EXPECT_FLOAT_EQ(uph.x, 3); + EXPECT_FLOAT_EQ(uph.y, 7); + EXPECT_FLOAT_EQ(uph.z, 4); + EXPECT_FLOAT_EQ(uph.w, 8); + auto l2h = a.l2h(b); + EXPECT_FLOAT_EQ(l2h.x, 1); + EXPECT_FLOAT_EQ(l2h.y, 2); + EXPECT_FLOAT_EQ(l2h.z, 5); + EXPECT_FLOAT_EQ(l2h.w, 6); + auto h2l = a.h2l(b); + EXPECT_FLOAT_EQ(h2l.x, 7); + EXPECT_FLOAT_EQ(h2l.y, 8); + EXPECT_FLOAT_EQ(h2l.z, 3); + EXPECT_FLOAT_EQ(h2l.w, 4); +} + +TEST(GSVector4Test, BroadcastAndInsertExtract) +{ + GSVector4 v(9, 2, 3, 4); + auto bc_self = v.broadcast32(); + EXPECT_FLOAT_EQ(bc_self.x, 9); + EXPECT_FLOAT_EQ(bc_self.y, 9); + EXPECT_FLOAT_EQ(bc_self.z, 9); + EXPECT_FLOAT_EQ(bc_self.w, 9); + + auto bc_static = GSVector4::broadcast32(v); + EXPECT_FLOAT_EQ(bc_static.z, 9); + + GSVector4 a(1, 2, 3, 4); + GSVector4 b(5, 6, 7, 8); + auto ins_from_other = a.insert32<2, 0>(b); // copy b.z into a.x + EXPECT_FLOAT_EQ(ins_from_other.x, 7); + EXPECT_FLOAT_EQ(ins_from_other.y, 2); + EXPECT_FLOAT_EQ(ins_from_other.z, 3); + EXPECT_FLOAT_EQ(ins_from_other.w, 4); + + auto ins_scalar = a.insert32<1>(42.0f); + EXPECT_FLOAT_EQ(ins_scalar.x, 1); + EXPECT_FLOAT_EQ(ins_scalar.y, 42.0f); + EXPECT_FLOAT_EQ(ins_scalar.z, 3); + EXPECT_FLOAT_EQ(ins_scalar.w, 4); + + EXPECT_FLOAT_EQ(a.extract32<0>(), 1.0f); + EXPECT_FLOAT_EQ(a.extract32<3>(), 4.0f); +} + +TEST(GSVector4Test, BitwiseAndAndNot) +{ + GSVector4 a; + a.U32[0] = 0xFFFFFFFFu; + a.U32[1] = 0x00FF00FFu; + a.U32[2] = 0x12345678u; + a.U32[3] = 0xAAAAAAAAu; + + GSVector4 b; + b.U32[0] = 0x0F0F0F0Fu; + b.U32[1] = 0xFF00FF00u; + b.U32[2] = 0xFFFFFFFFu; + b.U32[3] = 0x55555555u; + + auto vand = a & b; + EXPECT_EQ(vand.U32[0], 0x0F0F0F0Fu); + EXPECT_EQ(vand.U32[1], 0x00000000u); + EXPECT_EQ(vand.U32[2], 0x12345678u); + EXPECT_EQ(vand.U32[3], 0x00000000u); + + auto vor = a | b; + EXPECT_EQ(vor.U32[0], 0xFFFFFFFFu); + EXPECT_EQ(vor.U32[1], 0xFFFFFFFFu); + EXPECT_EQ(vor.U32[2], 0xFFFFFFFFu); + EXPECT_EQ(vor.U32[3], 0xFFFFFFFFu); + + auto vxor = a ^ b; + EXPECT_EQ(vxor.U32[0], 0xF0F0F0F0u); + EXPECT_EQ(vxor.U32[1], 0xFFFFFFFFu); + EXPECT_EQ(vxor.U32[2], 0xEDCBA987u); + EXPECT_EQ(vxor.U32[3], 0xFFFFFFFFu); + + auto an = a.andnot(b); // (~b) & a + EXPECT_EQ(an.U32[0], (~b.U32[0]) & a.U32[0]); + EXPECT_EQ(an.U32[1], (~b.U32[1]) & a.U32[1]); + EXPECT_EQ(an.U32[2], (~b.U32[2]) & a.U32[2]); + EXPECT_EQ(an.U32[3], (~b.U32[3]) & a.U32[3]); +} + +TEST(GSVector4Test, ReplaceNaN) +{ + GSVector4 v(1.0f, std::numeric_limits::quiet_NaN(), -5.0f, std::numeric_limits::quiet_NaN()); + GSVector4 repl(10.0f, 20.0f, 30.0f, 40.0f); + auto r = v.replace_nan(repl); + EXPECT_FLOAT_EQ(r.x, 1.0f); // kept + EXPECT_FLOAT_EQ(r.y, 20.0f); // replaced + EXPECT_FLOAT_EQ(r.z, -5.0f); // kept + EXPECT_FLOAT_EQ(r.w, 40.0f); // replaced +} + +TEST(GSVector4Test, DoubleExtendedOps) +{ + GSVector4 d = GSVector4::f64(-4.0, 9.0); + auto sq = d.sqr64(); + EXPECT_DOUBLE_EQ(sq.F64[0], 16.0); + EXPECT_DOUBLE_EQ(sq.F64[1], 81.0); + + auto rt = GSVector4::f64(4.0, 9.0).sqrt64(); + EXPECT_DOUBLE_EQ(rt.F64[0], 2.0); + EXPECT_DOUBLE_EQ(rt.F64[1], 3.0); + + auto ab = d.abs64(); + EXPECT_DOUBLE_EQ(ab.F64[0], 4.0); + EXPECT_DOUBLE_EQ(ab.F64[1], 9.0); + + auto ng = d.neg64(); + EXPECT_DOUBLE_EQ(ng.F64[0], 4.0); + EXPECT_DOUBLE_EQ(ng.F64[1], -9.0); + + GSVector4 d2 = GSVector4::f64(-2.0, 10.0); + EXPECT_DOUBLE_EQ(d.min64(d2).F64[0], -4.0); + EXPECT_DOUBLE_EQ(d.min64(d2).F64[1], 9.0); + EXPECT_DOUBLE_EQ(d.max64(d2).F64[0], -2.0); + EXPECT_DOUBLE_EQ(d.max64(d2).F64[1], 10.0); + + auto gt = d.gt64(d2); + EXPECT_EQ(gt.U64[0], 0ULL); // -4 > -2 ? no + EXPECT_EQ(gt.U64[1], 0ULL); // 9 > 10 ? no + auto lt = d.lt64(d2); + EXPECT_NE(lt.U64[0], 0ULL); // -4 < -2 + EXPECT_NE(lt.U64[1], 0ULL); // 9 < 10 + auto ge = d.ge64(d2); + EXPECT_EQ(ge.U64[0], 0ULL); // -4 >= -2 ? no + EXPECT_EQ(ge.U64[1], 0ULL); // 9 >= 10 ? no + auto le = d.le64(d2); + EXPECT_NE(le.U64[0], 0ULL); + EXPECT_NE(le.U64[1], 0ULL); + auto eq = d.eq64(d); + EXPECT_EQ(eq.U64[0], 0xFFFFFFFFFFFFFFFFULL); + EXPECT_EQ(eq.U64[1], 0xFFFFFFFFFFFFFFFFULL); +} + +TEST(GSVector4Test, FloatToDoubleConversions) +{ + GSVector4 vf(1.25f, 2.75f, 3.0f, 4.0f); + auto fd = GSVector4::f32to64(vf); + EXPECT_DOUBLE_EQ(fd.F64[0], 1.25); + EXPECT_DOUBLE_EQ(fd.F64[1], 2.75); + + GSVector4 vd = GSVector4::f64(5.9, -2.1); + auto i32 = vd.f64toi32(); + EXPECT_EQ(i32.S32[0], 5); + EXPECT_EQ(i32.S32[1], -2); +} + +// Cross-class conversion tests +TEST(GSVectorTest, ConversionsGSVector2iGSVector2) +{ + GSVector2i vi(42, 84); + GSVector2 vf(vi); + EXPECT_FLOAT_EQ(vf.x, 42.0f); + EXPECT_FLOAT_EQ(vf.y, 84.0f); + + GSVector2 vf2(3.14f, 2.71f); + GSVector2i vi2(vf2); + EXPECT_EQ(vi2.x, 3); + EXPECT_EQ(vi2.y, 2); + + // Test cast operations + auto cast_result = GSVector2::cast(vi); + // Cast preserves bit pattern, so we can't directly compare float values + EXPECT_EQ(cast_result.I32[0], 42); + EXPECT_EQ(cast_result.I32[1], 84); + + auto cast_result2 = GSVector2i::cast(vf2); + // Cast preserves bit pattern + EXPECT_EQ(cast_result2.U32[0], vf2.U32[0]); + EXPECT_EQ(cast_result2.U32[1], vf2.U32[1]); +} diff --git a/src/common-tests/hash_tests.cpp b/src/common-tests/hash_tests.cpp new file mode 100644 index 000000000..31bfd2bbf --- /dev/null +++ b/src/common-tests/hash_tests.cpp @@ -0,0 +1,374 @@ +// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin +// 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 + +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 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(reinterpret_cast(empty_string.data()), 0)); + EXPECT_EQ(result, expected); +} + +TEST(MD5Digest, SingleCharacter) +{ + // MD5 hash of "a": 0cc175b9c0f1b6a831c399e269772661 + static constexpr std::array 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(reinterpret_cast(test_string.data()), test_string.size())); + EXPECT_EQ(result, expected); +} + +TEST(MD5Digest, ABC) +{ + // MD5 hash of "abc": 900150983cd24fb0d6963f7d28e17f72 + static constexpr std::array 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(reinterpret_cast(test_string.data()), test_string.size())); + EXPECT_EQ(result, expected); +} + +TEST(MD5Digest, MessageDigest) +{ + // MD5 hash of "message digest": f96b697d7cb7938d525a2f31aaf161d0 + static constexpr std::array 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(reinterpret_cast(test_string.data()), test_string.size())); + EXPECT_EQ(result, expected); +} + +TEST(MD5Digest, Alphabet) +{ + // MD5 hash of "abcdefghijklmnopqrstuvwxyz": c3fcd3d76192e4007dfb496cca67e13b + static constexpr std::array 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(reinterpret_cast(test_string.data()), test_string.size())); + EXPECT_EQ(result, expected); +} + +TEST(MD5Digest, AlphaNumeric) +{ + // MD5 hash of "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789": d174ab98d277d9f5a5611c2c9f419d9f + static constexpr std::array 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(reinterpret_cast(test_string.data()), test_string.size())); + EXPECT_EQ(result, expected); +} + +TEST(MD5Digest, LongString) +{ + // MD5 hash of 1000000 'a' characters: 7707d6ae4e027c70eea2a935c2296f21 + static constexpr std::array 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 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(reinterpret_cast(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 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(test_string.size())); + std::array result1; + digest.Final(result1); + + // Reset and compute again + digest.Reset(); + digest.Update(test_string.data(), static_cast(test_string.size())); + std::array result2; + digest.Final(result2); + + EXPECT_EQ(result1, result2); +} + +TEST(MD5Digest, SpanInterface) +{ + const std::string test_string = "test with span interface"; + std::span test_span(reinterpret_cast(test_string.data()), test_string.size()); + + // Test Update with span + MD5Digest digest; + digest.Update(test_span); + std::array 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 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 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 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 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 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 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 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 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 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 test_span(reinterpret_cast(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 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 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 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(binary_data, sizeof(binary_data))); + + MD5Digest digest; + digest.Update(binary_data, sizeof(binary_data)); + std::array result2; + digest.Final(result2); + + EXPECT_EQ(result1, result2); +} diff --git a/src/common-tests/sha256_tests.cpp b/src/common-tests/sha256_tests.cpp deleted file mode 100644 index 9f79d6004..000000000 --- a/src/common-tests/sha256_tests.cpp +++ /dev/null @@ -1,34 +0,0 @@ -// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin -// SPDX-License-Identifier: CC-BY-NC-ND-4.0 - -#include "common/sha256_digest.h" - -#include - -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); -} diff --git a/src/common-tests/string_tests.cpp b/src/common-tests/string_tests.cpp index 8a806c0e7..8364e0b0c 100644 --- a/src/common-tests/string_tests.cpp +++ b/src/common-tests/string_tests.cpp @@ -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("123"); + ASSERT_TRUE(result.has_value()); + ASSERT_EQ(*result, 123); + + result = StringUtil::FromChars("-456"); + ASSERT_TRUE(result.has_value()); + ASSERT_EQ(*result, -456); + + // Test hex + auto hex_result = StringUtil::FromChars("FF", 16); + ASSERT_TRUE(hex_result.has_value()); + ASSERT_EQ(*hex_result, 255); + + // Test invalid input + auto invalid = StringUtil::FromChars("abc"); + ASSERT_FALSE(invalid.has_value()); + + // Test with endptr + std::string_view endptr; + auto endptr_result = StringUtil::FromChars("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("0xFF"); + ASSERT_TRUE(hex.has_value()); + ASSERT_EQ(*hex, 255); + + // Test binary prefix + auto bin = StringUtil::FromCharsWithOptionalBase("0b1010"); + ASSERT_TRUE(bin.has_value()); + ASSERT_EQ(*bin, 10); + + // Test octal prefix + auto oct = StringUtil::FromCharsWithOptionalBase("0123"); + ASSERT_TRUE(oct.has_value()); + ASSERT_EQ(*oct, 83); // 123 in octal = 83 in decimal + + // Test decimal (no prefix) + auto dec = StringUtil::FromCharsWithOptionalBase("123"); + ASSERT_TRUE(dec.has_value()); + ASSERT_EQ(*dec, 123); +} + +TEST(StringUtil, FromCharsFloatingPoint) +{ + auto result = StringUtil::FromChars("123.45"); + ASSERT_TRUE(result.has_value()); + ASSERT_FLOAT_EQ(*result, 123.45f); + + auto double_result = StringUtil::FromChars("-456.789"); + ASSERT_TRUE(double_result.has_value()); + ASSERT_DOUBLE_EQ(*double_result, -456.789); + + // Test scientific notation + auto sci = StringUtil::FromChars("1.23e-4"); + ASSERT_TRUE(sci.has_value()); + ASSERT_DOUBLE_EQ(*sci, 0.000123); + + // Test invalid + auto invalid = StringUtil::FromChars("abc"); + ASSERT_FALSE(invalid.has_value()); +} + +TEST(StringUtil, FromCharsBool) +{ + // Test true values + ASSERT_TRUE(StringUtil::FromChars("true", 10).value_or(false)); + ASSERT_TRUE(StringUtil::FromChars("TRUE", 10).value_or(false)); + ASSERT_TRUE(StringUtil::FromChars("yes", 10).value_or(false)); + ASSERT_TRUE(StringUtil::FromChars("YES", 10).value_or(false)); + ASSERT_TRUE(StringUtil::FromChars("on", 10).value_or(false)); + ASSERT_TRUE(StringUtil::FromChars("ON", 10).value_or(false)); + ASSERT_TRUE(StringUtil::FromChars("1", 10).value_or(false)); + ASSERT_TRUE(StringUtil::FromChars("enabled", 10).value_or(false)); + ASSERT_TRUE(StringUtil::FromChars("ENABLED", 10).value_or(false)); + + // Test false values + ASSERT_FALSE(StringUtil::FromChars("false", 10).value_or(true)); + ASSERT_FALSE(StringUtil::FromChars("FALSE", 10).value_or(true)); + ASSERT_FALSE(StringUtil::FromChars("no", 10).value_or(true)); + ASSERT_FALSE(StringUtil::FromChars("NO", 10).value_or(true)); + ASSERT_FALSE(StringUtil::FromChars("off", 10).value_or(true)); + ASSERT_FALSE(StringUtil::FromChars("OFF", 10).value_or(true)); + ASSERT_FALSE(StringUtil::FromChars("0", 10).value_or(true)); + ASSERT_FALSE(StringUtil::FromChars("disabled", 10).value_or(true)); + ASSERT_FALSE(StringUtil::FromChars("DISABLED", 10).value_or(true)); + + // Test invalid + ASSERT_FALSE(StringUtil::FromChars("maybe", 10).has_value()); + ASSERT_FALSE(StringUtil::FromChars("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 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(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 buffer(8); + size_t decoded = StringUtil::DecodeHex(std::span(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 data = {1, 2, 3, 4, 5}; + ASSERT_EQ(StringUtil::EncodedBase64Length(std::span(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 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 empty_list; + ASSERT_FALSE(StringUtil::IsInStringList(empty_list, "apple")); +} + +TEST(StringUtil, AddToStringList) +{ + std::vector 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 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 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 empty_list; + ASSERT_EQ(StringUtil::JoinString(empty_list, ','), ""); + + // Test single item + std::vector 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 data = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}; + + // Test exact match + auto result = StringUtil::BytePatternSearch(std::span(data), "01 02 03"); + ASSERT_TRUE(result.has_value()); + ASSERT_EQ(result.value(), 0u); + + // Test match in middle + result = StringUtil::BytePatternSearch(std::span(data), "03 04 05"); + ASSERT_TRUE(result.has_value()); + ASSERT_EQ(result.value(), 2u); + + // Test with wildcards + result = StringUtil::BytePatternSearch(std::span(data), "01 ?? 03"); + ASSERT_TRUE(result.has_value()); + ASSERT_EQ(result.value(), 0u); + + // Test no match + result = StringUtil::BytePatternSearch(std::span(data), "FF FF FF"); + ASSERT_FALSE(result.has_value()); + + // Test empty pattern + result = StringUtil::BytePatternSearch(std::span(data), ""); + ASSERT_FALSE(result.has_value()); + + // Test lowercase hex + result = StringUtil::BytePatternSearch(std::span(data), "01 02 03"); + ASSERT_TRUE(result.has_value()); + ASSERT_EQ(result.value(), 0u); + + // Test mixed case + result = StringUtil::BytePatternSearch(std::span(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 diff --git a/src/common/gsvector_neon.h b/src/common/gsvector_neon.h index 92712e240..4c0edc5b1 100644 --- a/src/common/gsvector_neon.h +++ b/src/common/gsvector_neon.h @@ -1846,26 +1846,6 @@ public: return GSVector4i(vreinterpretq_s32_s64(vshlq_s64(vreinterpretq_s64_s32(v4s), vreinterpretq_s64_s32(v.v4s)))); } - template - 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 ALWAYS_INLINE GSVector4i srl64() const { diff --git a/src/common/gsvector_nosimd.h b/src/common/gsvector_nosimd.h index 547a37ce9..110c91a91 100644 --- a/src/common/gsvector_nosimd.h +++ b/src/common/gsvector_nosimd.h @@ -1377,16 +1377,6 @@ public: GSVector4i srlv64(const GSVector4i& v) const { ALL_LANES_64(ret.U64[i] = U64[i] >> v.U64[i]); } - template - 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]); } diff --git a/src/common/gsvector_sse.h b/src/common/gsvector_sse.h index 4c93b72fb..53e8d757e 100644 --- a/src/common/gsvector_sse.h +++ b/src/common/gsvector_sse.h @@ -1538,18 +1538,6 @@ public: ALWAYS_INLINE GSVector4i srlv64(const GSVector4i& v) const { return GSVector4i(_mm_srlv_epi64(m, v.m)); } #endif - template - 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(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(f)); + return GSVector4(_mm_shuffle_ps(v, v, _MM_SHUFFLE(0, 0, 0, 0))); + } + #endif ALWAYS_INLINE static GSVector4 broadcast64(const void* d)