Move ComputeTest,GraphicsTest,RenderTest and Std140Debug to the tests folder

Also renamed NazaraUnitTests to UnitTests
This commit is contained in:
SirLynix
2022-12-26 08:44:11 +01:00
parent fe8715f1fb
commit 4b804dc613
71 changed files with 33 additions and 36 deletions

View File

@@ -0,0 +1,28 @@
#include <Nazara/Core/AbstractHash.hpp>
#include <catch2/catch_approx.hpp>
#include <catch2/catch_test_macros.hpp>
#include <Nazara/Core/ByteArray.hpp>
#include <array>
SCENARIO("AbstractHash", "[CORE][ABSTRACTHASH]")
{
GIVEN("The hash SHA512")
{
std::unique_ptr<Nz::AbstractHash> SHA512 = Nz::AbstractHash::Get(Nz::HashType::SHA512);
SHA512->Begin();
WHEN("We introduce data")
{
std::array<Nz::UInt8, 4> array{ { 0, 1, 2, 3 } };
SHA512->Append(array.data(), array.size());
THEN("We ask for the bytearray")
{
Nz::ByteArray byteArray = SHA512->End();
CHECK(byteArray.GetSize() == SHA512->GetDigestLength());
}
}
}
}

View File

@@ -0,0 +1,99 @@
#include <Nazara/Core/Algorithm.hpp>
#include <Nazara/Core/File.hpp>
#include <Nazara/Core/StringExt.hpp>
#include <Nazara/Math/Vector2.hpp>
#include <catch2/catch_approx.hpp>
#include <catch2/catch_test_macros.hpp>
#include <array>
#include <filesystem>
#include <variant>
std::filesystem::path GetAssetDir();
TEST_CASE("ComputeHash", "[CORE][ALGORITHM]")
{
std::filesystem::path testFilePath = GetAssetDir() / "Logo.png";
struct Test
{
Nz::HashType hashType;
std::variant<const char*, std::filesystem::path> input;
const char* expectedOutput;
};
static_assert(Nz::CRC32("Nazara Engine") == 0x8A2F5235);
static_assert(Nz::CRC32("The quick brown fox jumps over the lazy dog") == 0x414FA339);
CHECK(Nz::CRC32("Nazara Engine") == 0x8A2F5235);
CHECK(Nz::CRC32("The quick brown fox jumps over the lazy dog") == 0x414FA339);
// https://defuse.ca/checksums.htm
// https://toolslick.com/programming/hashing/crc-calculator
std::array tests{
//Test{ Nz::HashType::CRC16, "Nazara Engine", "9412" },
Test{ Nz::HashType::CRC32, "Nazara Engine", "8A2F5235" },
Test{ Nz::HashType::CRC64, "Nazara Engine", "87211217C5FFCDDD" },
Test{ Nz::HashType::Fletcher16, "Nazara Engine", "71D7" },
Test{ Nz::HashType::MD5, "Nazara Engine", "71FF4EC3B56010ABC03E4B2C1C8A14B9" },
Test{ Nz::HashType::SHA1, "Nazara Engine", "FCE7C077A1ED0881A8C60C4822BB1369B672CA5B" },
Test{ Nz::HashType::SHA224, "Nazara Engine", "673677224E9B0D24C3828FE6CD36A37C2D43BA27C31B6E6E54756BD4" },
Test{ Nz::HashType::SHA256, "Nazara Engine", "7E66722931B65BF780FB55D41834C3D67455029CC1CB497976D2D41A90D3FD4C" },
Test{ Nz::HashType::SHA384, "Nazara Engine", "80064D11A4E4C2A44DE03406E03025C52641E04BA80DE78B1BB0BA6EA577B4B6914F2BDED5B95BB7285F8EA785B9B996" },
Test{ Nz::HashType::SHA512, "Nazara Engine", "C3A8212B61B88D77E8C4B40884D49BA6A54202865CAA847F676D2EA20E60F43B1C8024DE982A214EB3670B752AF3EE37189F1EBDCA608DD0DD427D8C19371FA5" },
Test{ Nz::HashType::Whirlpool, "Nazara Engine", "92113DC95C25057C4154E9A8B2A4C4C800D24DD22FA7D796F300AF9C4EFA4FAAB6030F66B0DC74B270A911DA18E007544B79B84440A1D58AA7C79A73C39C29F8" },
//Test{ Nz::HashType::CRC16, "The quick brown fox jumps over the lazy dog", "FCDF" },
Test{ Nz::HashType::CRC32, "The quick brown fox jumps over the lazy dog", "414FA339" },
Test{ Nz::HashType::CRC64, "The quick brown fox jumps over the lazy dog", "41E05242FFA9883B" },
Test{ Nz::HashType::Fletcher16, "The quick brown fox jumps over the lazy dog", "FEE8" },
Test{ Nz::HashType::MD5, "The quick brown fox jumps over the lazy dog", "9E107D9D372BB6826BD81D3542A419D6" },
Test{ Nz::HashType::SHA1, "The quick brown fox jumps over the lazy dog", "2FD4E1C67A2D28FCED849EE1BB76E7391B93EB12" },
Test{ Nz::HashType::SHA224, "The quick brown fox jumps over the lazy dog", "730E109BD7A8A32B1CB9D9A09AA2325D2430587DDBC0C38BAD911525" },
Test{ Nz::HashType::SHA256, "The quick brown fox jumps over the lazy dog", "D7A8FBB307D7809469CA9ABCB0082E4F8D5651E46D3CDB762D02D0BF37C9E592" },
Test{ Nz::HashType::SHA384, "The quick brown fox jumps over the lazy dog", "CA737F1014A48F4C0B6DD43CB177B0AFD9E5169367544C494011E3317DBF9A509CB1E5DC1E85A941BBEE3D7F2AFBC9B1" },
Test{ Nz::HashType::SHA512, "The quick brown fox jumps over the lazy dog", "07E547D9586F6A73F73FBAC0435ED76951218FB7D0C8D788A309D785436BBB642E93A252A954F23912547D1E8A3B5ED6E1BFD7097821233FA0538F3DB854FEE6" },
Test{ Nz::HashType::Whirlpool, "The quick brown fox jumps over the lazy dog", "B97DE512E91E3828B40D2B0FDCE9CEB3C4A71F9BEA8D88E75C4FA854DF36725FD2B52EB6544EDCACD6F8BEDDFEA403CB55AE31F03AD62A5EF54E42EE82C3FB35" },
//Test{ Nz::HashType::CRC16, testFilePath, "30A6" },
Test{ Nz::HashType::CRC32, testFilePath, "5A2024CD" },
Test{ Nz::HashType::CRC64, testFilePath, "F92157F70C713EEC" },
Test{ Nz::HashType::Fletcher16, testFilePath, "9152" },
Test{ Nz::HashType::MD5, testFilePath, "75B35EDB2DB8B4ED5020821E401B1FA3" },
Test{ Nz::HashType::SHA1, testFilePath, "FD6B51F6E176AA91BDDCFAFB6D65AF97116C3981" },
Test{ Nz::HashType::SHA224, testFilePath, "77F8536DB2AEDDB0B18747EF7907AEFFE019C3EB6C9E64CC31D2E4DA" },
Test{ Nz::HashType::SHA256, testFilePath, "5C4B9387327C039A6CE9ED51983D6C2ADA9F9DD01D024C2D5D588237ADFC7423" },
Test{ Nz::HashType::SHA384, testFilePath, "BB11F442F14AED77C116C6CA05103C006D40F861D23D272263BBD9F0D260FA5449B728A94F80BB807BD16778C558EF05" },
Test{ Nz::HashType::SHA512, testFilePath, "1EA50E73C1D7D8DFCD51AC2718D7EB953E4B2D1D9EFA06F5B89DC1B0315C6C57A007D733DFA4DA41BFCE0E73446EFAF3413E8C1D38A0C327773AFF49C010F091" },
Test{ Nz::HashType::Whirlpool, testFilePath, "A1598870C7E5C59004CD0A2C7E17248606E2DDD832EED1B0E2D52A9A72842A073CC2F889D2EC71C061A21A86879FF009D4FED1010B454FF8535C401BC9A60F64" },
};
for (const Test& test : tests)
{
auto hash = Nz::AbstractHash::Get(test.hashType);
if (std::holds_alternative<const char*>(test.input))
{
const char* inputStr = std::get<const char*>(test.input);
WHEN("We compute " << hash->GetHashName() << " of '" << inputStr << "'")
{
auto result = Nz::ComputeHash(*hash, inputStr);
CHECK(result.GetSize() == hash->GetDigestLength());
CHECK(Nz::ToUpper(result.ToHex()) == test.expectedOutput);
}
}
else
{
assert(std::holds_alternative<std::filesystem::path>(test.input));
const std::filesystem::path& filePath = std::get<std::filesystem::path>(test.input);
Nz::File file(filePath);
WHEN("We compute " << hash->GetHashName() << " of " << filePath << " file")
{
auto result = Nz::ComputeHash(*hash, file);
CHECK(result.GetSize() == hash->GetDigestLength());
CHECK(Nz::ToUpper(result.ToHex()) == test.expectedOutput);
}
}
}
}

View File

@@ -0,0 +1,85 @@
#include <Nazara/Core/MemoryView.hpp>
#include <catch2/catch_approx.hpp>
#include <catch2/catch_test_macros.hpp>
#include <cstring>
SCENARIO("Buffering", "[CORE][BUFFERING]")
{
const char mem[] = "abcdefghijklmnopqrstuvwxyz";
for (std::size_t bufferSize : { 1, 2, 3, 4, 6, 7, 0xFFFF })
{
Nz::MemoryView memView(mem, sizeof(mem));
memView.EnableBuffering(true, bufferSize);
WHEN("Using a buffer size of " + std::to_string(bufferSize))
{
CHECK(memView.GetCursorPos() == 0);
std::vector<char> readBuffer;
WHEN("Reading the full buffer")
{
readBuffer.resize(sizeof(mem));
REQUIRE(memView.Read(readBuffer.data(), readBuffer.size()) == readBuffer.size());
CHECK(std::memcmp(readBuffer.data(), mem, readBuffer.size()) == 0);
CHECK(memView.GetCursorPos() == sizeof(mem));
}
WHEN("Reading 2 bytes")
{
readBuffer.resize(2);
REQUIRE(memView.Read(readBuffer.data(), readBuffer.size()) == readBuffer.size());
CHECK(std::memcmp(readBuffer.data(), "ab", readBuffer.size()) == 0);
CHECK(memView.GetCursorPos() == 2);
REQUIRE(memView.Read(readBuffer.data(), readBuffer.size()) == readBuffer.size());
CHECK(std::memcmp(readBuffer.data(), "cd", readBuffer.size()) == 0);
CHECK(memView.GetCursorPos() == 4);
WHEN("Reading 4 bytes and reading")
{
readBuffer.resize(4);
REQUIRE(memView.Read(readBuffer.data(), readBuffer.size()) == readBuffer.size());
CHECK(std::memcmp(readBuffer.data(), "efgh", readBuffer.size()) == 0);
CHECK(memView.GetCursorPos() == 8);
AND_WHEN("Seeking at 10")
{
memView.SetCursorPos(10);
CHECK(memView.GetCursorPos() == 10);
readBuffer.resize(2);
REQUIRE(memView.Read(readBuffer.data(), readBuffer.size()) == readBuffer.size());
CHECK(std::memcmp(readBuffer.data(), "kl", readBuffer.size()) == 0);
CHECK(memView.GetCursorPos() == 12);
}
AND_WHEN("Seeking at 6 and reading")
{
memView.SetCursorPos(6);
CHECK(memView.GetCursorPos() == 6);
readBuffer.resize(2);
REQUIRE(memView.Read(readBuffer.data(), readBuffer.size()) == readBuffer.size());
CHECK(std::memcmp(readBuffer.data(), "ghij", readBuffer.size()) == 0);
CHECK(memView.GetCursorPos() == 8);
}
}
}
WHEN("Reading 6 then 2 bytes")
{
readBuffer.resize(6);
REQUIRE(memView.Read(readBuffer.data(), readBuffer.size()) == readBuffer.size());
CHECK(std::memcmp(readBuffer.data(), "abcdef", readBuffer.size()) == 0);
CHECK(memView.GetCursorPos() == 6);
readBuffer.resize(2);
REQUIRE(memView.Read(readBuffer.data(), readBuffer.size()) == readBuffer.size());
CHECK(std::memcmp(readBuffer.data(), "gh", readBuffer.size()) == 0);
CHECK(memView.GetCursorPos() == 8);
}
}
}
}

View File

@@ -0,0 +1,237 @@
#include <Nazara/Core/ByteArray.hpp>
#include <catch2/catch_approx.hpp>
#include <catch2/catch_test_macros.hpp>
#include <string>
SCENARIO("ByteArray", "[CORE][BYTEARRAY]")
{
GIVEN("Allocate and raw constructor")
{
Nz::ByteArray byteArray(3);
THEN("Capacity is 3 and size is 0")
{
REQUIRE(byteArray.GetSize() == 0);
REQUIRE(byteArray.GetCapacity() >= 3);
}
WHEN("We add 'data'")
{
byteArray.Append("data", 4);
THEN("We get 'data'")
{
REQUIRE(byteArray.GetSize() == 4);
REQUIRE(byteArray.GetCapacity() >= 4);
REQUIRE(byteArray == Nz::ByteArray("data", 4));
REQUIRE(byteArray.ToString() == "data");
}
}
}
GIVEN("Iterator and default constructor")
{
std::string anotherDataString("anotherData");
Nz::ByteArray defaultByte;
Nz::ByteArray anotherData(anotherDataString.begin(), anotherDataString.end());
WHEN("We assign 'anotherData' with iterator")
{
defaultByte.Assign(anotherData.begin(), anotherData.end());
REQUIRE(anotherData == defaultByte);
REQUIRE(defaultByte.GetSize() == 11);
REQUIRE(defaultByte.GetCapacity() >= 11);
REQUIRE(anotherData.GetSize() == 11);
REQUIRE(anotherData.GetCapacity() >= 11);
}
}
GIVEN("Copy and Move constructor")
{
Nz::ByteArray originalArray(3, 64);
WHEN("We copy")
{
Nz::ByteArray copyByteArray(originalArray);
THEN("We get a copy")
{
REQUIRE(copyByteArray == originalArray);
AND_WHEN("We modify one")
{
for (Nz::ByteArray::size_type i = 0; i < copyByteArray.GetSize(); ++i)
copyByteArray[i] = 46;
THEN("They are no more equal")
{
REQUIRE(copyByteArray != originalArray);
REQUIRE(copyByteArray == Nz::ByteArray(3, 46));
REQUIRE(copyByteArray.GetConstBuffer() != originalArray.GetConstBuffer());
}
}
}
}
WHEN("We move")
{
Nz::ByteArray moveByteArray(std::move(originalArray));
THEN("These results are expected")
{
REQUIRE(moveByteArray == Nz::ByteArray(3, 64));
CHECK(originalArray.IsEmpty());
REQUIRE(originalArray.GetCapacity() == 0);
REQUIRE(moveByteArray.GetConstBuffer() != originalArray.GetConstBuffer());
AND_WHEN("We modify the empty one")
{
originalArray.Prepend(Nz::ByteArray(3, 64));
THEN("They are no more equal")
{
REQUIRE(moveByteArray == originalArray);
REQUIRE(moveByteArray.GetConstBuffer() != originalArray.GetConstBuffer());
}
}
}
}
}
GIVEN("Two byte array (abc) and (cba)")
{
Nz::ByteArray abc("abc", 3);
Nz::ByteArray cba;
cba = Nz::ByteArray("cba", 3);
WHEN("We do some antagonists operations")
{
THEN("These results are expected")
{
REQUIRE(abc.Back() == cba.Front());
abc.Erase(abc.begin(), abc.begin() + 1);
abc.Erase(abc.begin());
cba.Erase(cba.end() - 1, cba.end());
cba.Erase(cba.end() - 1);
REQUIRE(abc == Nz::ByteArray("c", 1));
REQUIRE(cba == Nz::ByteArray("c", 1));
std::string ab("ab");
abc.Insert(abc.begin(), ab.begin(), ab.end());
cba += Nz::ByteArray("ba", 2);
REQUIRE(abc == Nz::ByteArray("abc", 3));
REQUIRE(cba == Nz::ByteArray("cba", 3));
abc.PopBack();
cba.PopFront();
REQUIRE(abc == Nz::ByteArray("ab", 2));
REQUIRE(cba == Nz::ByteArray("ba", 2));
abc.PushBack('c');
cba.PushFront('c');
REQUIRE(abc == Nz::ByteArray("abc", 3));
REQUIRE(cba == Nz::ByteArray("cba", 3));
}
}
}
GIVEN("One byte array of capacity 10")
{
Nz::ByteArray capacityArray(10);
WHEN("We reserve for 100")
{
capacityArray.Reserve(100);
THEN("Capacity is 100")
{
REQUIRE(capacityArray.GetCapacity() == 100);
}
AND_WHEN("We add information and then shrink to fit")
{
capacityArray.Prepend("information", 11);
capacityArray.ShrinkToFit();
THEN("Capacity is 11")
{
REQUIRE(capacityArray.GetCapacity() == 11);
REQUIRE(capacityArray.GetSize() == 11);
}
}
}
Nz::ByteArray::const_pointer oldBuffer = capacityArray.GetConstBuffer();
WHEN("We reserve for 5, add 'data' for 4 and then shrink to fit")
{
capacityArray.Reserve(5);
THEN("Capacity is still 10")
{
REQUIRE(capacityArray.GetCapacity() == 10);
REQUIRE(capacityArray.GetSize() == 0);
REQUIRE(capacityArray.GetConstBuffer() == oldBuffer);
}
capacityArray.Append("data", 4);
capacityArray.ShrinkToFit();
THEN("Capacity is 4")
{
REQUIRE(capacityArray.GetConstBuffer() != oldBuffer);
REQUIRE(capacityArray.GetCapacity() == 4);
REQUIRE(capacityArray.GetSize() == 4);
}
}
}
GIVEN("Three byte array")
{
Nz::ByteArray first("hello", 5);
Nz::ByteArray second("world", 5);
Nz::ByteArray third;
WHEN("We swap first and third, then second and third and finally third and first")
{
Nz::ByteArray oldFirst(first);
Nz::ByteArray oldSecond(second);
first.Swap(third);
std::swap(second, third);
third.Swap(first);
THEN("First and second have been swapped and third is still empty.")
{
REQUIRE(oldFirst == second);
REQUIRE(oldSecond == first);
REQUIRE(third.IsEmpty());
}
}
}
GIVEN("A default byte array")
{
Nz::ByteArray defaultByteArray;
WHEN("We resize")
{
Nz::UInt64 oldSize = defaultByteArray.GetSize();
REQUIRE(oldSize == 0);
defaultByteArray.Resize(10);
THEN("Capacity has increased")
{
REQUIRE(defaultByteArray[oldSize] == 0);
REQUIRE(defaultByteArray.GetCapacity() >= 10);
REQUIRE(defaultByteArray.GetSize() == 10);
}
}
}
}

View File

@@ -0,0 +1,74 @@
#include <Nazara/Core/ByteStream.hpp>
#include <catch2/catch_approx.hpp>
#include <catch2/catch_test_macros.hpp>
#include <array>
SCENARIO("ByteStream", "[CORE][BYTESTREAM]")
{
GIVEN("A bytestream from a bunch of bytes")
{
const int numberOfBytes = 16;
std::array<Nz::Int8, numberOfBytes> data;
Nz::ByteStream byteStream(data.data(), numberOfBytes);
WHEN("We write some data in it")
{
int value = 5;
byteStream << value;
std::string string = "string";
byteStream << string;
byteStream.FlushBits();
THEN("We can retrieve them")
{
const void* const ptrData = data.data();
Nz::ByteStream readStream;
CHECK(readStream.GetSize() == 0);
readStream = Nz::ByteStream(ptrData, byteStream.GetSize());
int retrievedValue = 0;
readStream >> retrievedValue;
std::string retrievedString;
readStream >> retrievedString;
CHECK(value == retrievedValue);
CHECK(string == retrievedString);
}
}
}
GIVEN("A bytestream with a byte array and a different endianness")
{
const int numberOfBytes = 16;
Nz::ByteArray byteArray(numberOfBytes);
Nz::ByteStream byteStream(&byteArray);
byteStream.SetDataEndianness(Nz::GetPlatformEndianness() == Nz::Endianness::BigEndian ? Nz::Endianness::LittleEndian : Nz::Endianness::BigEndian);
WHEN("We write an integer")
{
int value = 7;
byteStream.Write(&value, sizeof(int));
bool boolean = true;
byteStream << boolean;
byteStream.FlushBits();
THEN("We can retrieve it properly")
{
Nz::ByteStream tmpStream(&byteArray);
tmpStream.SetDataEndianness(byteStream.GetDataEndianness());
int retrievedValue = 0;
tmpStream.Read(&retrievedValue, sizeof(int));
CHECK(value == retrievedValue);
Nz::ByteStream readStream(std::move(tmpStream));
bool retrievedBoolean = false;
readStream >> retrievedBoolean;
CHECK(boolean == retrievedBoolean);
}
}
}
}

View File

@@ -0,0 +1,51 @@
#include <Nazara/Core/Clock.hpp>
#include <catch2/catch_approx.hpp>
#include <catch2/catch_test_macros.hpp>
#include <chrono>
#include <thread>
SCENARIO("Clock", "[CORE][CLOCK]")
{
GIVEN("A clock paused")
{
Nz::UInt64 initialTime = 100;
Nz::Clock clock(initialTime, true);
WHEN("We get time since it is paused")
{
THEN("Time must be the initialTime")
{
CHECK(clock.GetMicroseconds() == initialTime);
CHECK(clock.IsPaused());
}
}
WHEN("We unpause it")
{
clock.Unpause();
REQUIRE(!clock.IsPaused());
THEN("Time must not be the initialTime")
{
std::this_thread::sleep_for(std::chrono::milliseconds(1));
Nz::UInt64 microSeconds = clock.GetMicroseconds();
CHECK(microSeconds != initialTime);
CHECK(microSeconds / 1000 <= clock.GetMilliseconds());
CHECK(microSeconds / (1000.f * 1000.f) <= clock.GetSeconds());
}
}
WHEN("We restart it")
{
clock.Restart();
THEN("It is unpaused and we can pause it")
{
CHECK(!clock.IsPaused());
clock.Pause();
CHECK(clock.IsPaused());
CHECK(clock.GetMicroseconds() != initialTime);
}
}
}
}

View File

@@ -0,0 +1,152 @@
#include <Nazara/Core/Color.hpp>
#include <catch2/catch_approx.hpp>
#include <catch2/catch_test_macros.hpp>
void CompareColor(const Nz::Color& lhs, const Nz::Color& rhs)
{
constexpr float epsilon = 0.1f;
REQUIRE(lhs.r == Catch::Approx(rhs.r).margin(epsilon));
REQUIRE(lhs.g == Catch::Approx(rhs.g).margin(epsilon));
REQUIRE(lhs.b == Catch::Approx(rhs.b).margin(epsilon));
REQUIRE(lhs.a == Catch::Approx(rhs.a).margin(epsilon));
}
constexpr float epsilon = 1.f;
void CompareCMY(const Nz::Color& color, float cyan, float magenta, float yellow)
{
float retrievedCyan = 0.f, retrievedMagenta = 0.f, retrievedYellow = 0.f;
Nz::Color::ToCMY(color, &retrievedCyan, &retrievedMagenta, &retrievedYellow);
CHECK(retrievedCyan == Catch::Approx(cyan).margin(epsilon));
CHECK(retrievedMagenta == Catch::Approx(magenta).margin(epsilon));
CHECK(retrievedYellow == Catch::Approx(yellow).margin(epsilon));
}
void CompareCMYK(const Nz::Color& color, float cyan, float magenta, float yellow, float black)
{
float retrievedCyan = 0.f, retrievedMagenta = 0.f, retrievedYellow = 0.f, retrievedBlack = 0.f;
Nz::Color::ToCMYK(color, &retrievedCyan, &retrievedMagenta, &retrievedYellow, &retrievedBlack);
CHECK(retrievedCyan == Catch::Approx(cyan).margin(epsilon));
CHECK(retrievedMagenta == Catch::Approx(magenta).margin(epsilon));
CHECK(retrievedYellow == Catch::Approx(yellow).margin(epsilon));
CHECK(retrievedBlack == Catch::Approx(black).margin(epsilon));
}
void CompareHSL(const Nz::Color& color, float hue, float saturation, float luminosity)
{
float retrievedHue = 0.f, retrievedSaturation = 0.f, retrievedLuminosity = 0.f;
Nz::Color::ToHSL(color, &retrievedHue, &retrievedSaturation, &retrievedLuminosity);
CHECK(retrievedHue == Catch::Approx(hue).margin(epsilon));
CHECK(retrievedSaturation == Catch::Approx(saturation).margin(epsilon));
CHECK(retrievedLuminosity == Catch::Approx(luminosity).margin(epsilon));
}
void CompareHSV(const Nz::Color& color, float hue, float saturation, float value)
{
float retrievedHue = 0.f, retrievedSaturation = 0.f, retrievedValue = 0.f;
Nz::Color::ToHSV(color, &retrievedHue, &retrievedSaturation, &retrievedValue);
CHECK(retrievedHue == Catch::Approx(hue).margin(epsilon));
CHECK(retrievedSaturation == Catch::Approx(saturation).margin(epsilon));
CHECK(retrievedValue == Catch::Approx(value).margin(epsilon));
}
void CompareXYZ(const Nz::Color& color, float x, float y, float z)
{
Nz::Vector3f retrievedValues = Nz::Vector3f::Zero();
Nz::Color::ToXYZ(color, &retrievedValues);
CHECK(retrievedValues.x == Catch::Approx(x).margin(epsilon));
CHECK(retrievedValues.y == Catch::Approx(y).margin(epsilon));
CHECK(retrievedValues.z == Catch::Approx(z).margin(epsilon));
}
SCENARIO("Color", "[CORE][COLOR]")
{
GIVEN("Two colors, one red (255) and one gray (128)")
{
Nz::Color red(1.f, 0.f, 0.f);
Nz::Color grey(0.5f);
WHEN("We do operations")
{
THEN("These results are expected")
{
red += Nz::Color(0, 0, 0);
grey *= Nz::Color(1.f);
CompareColor(red + grey, Nz::Color(1.5f, 0.5f, 0.5f, 3.f));
CompareColor(red * grey, Nz::Color(0.5f, 0.f, 0.f, 2.f));
}
}
}
GIVEN("A special color in different formats")
{
struct ColorData
{
std::string name;
Nz::Color rgb;
float cyan, magenta, yellow;
float cyanK, magentaK, yellowK, black;
float hue, saturation, luminosity;
float hueV, saturationV, valueV;
float x, y, z;
};
std::vector<ColorData> colors;
colors.push_back({
"blue",
Nz::Color(0.f, 0.f, 1.f),
1.f, 1.f, 0.f, // cmy
1.f, 1.f, 0.f, 0.f, // cmyk
240.f, 1.f, 0.5f, // hsl
240.f, 1.f, 1.f, // hsv
18.05f, 7.22f, 95.05f // xyz
});
colors.push_back({
"white",
Nz::Color(1.f, 1.f, 1.f),
0.f, 0.f, 0.f, // cmy
0.f, 0.f, 0.f, 0.f, // cmyk
0.f, 0.f, 1.f, // hsl
0.f, 0.f, 1.f, // hsv
95.05f, 100.f, 108.09f // xyz
});
colors.push_back({
"greenish",
Nz::Color(5 / 255.f, 191.f / 255.f, 25.f / 255.f),
0.980f, 0.251f, 0.902f, // cmy
0.974f, 0.000f, 0.869f, 0.251f, // cmyk
126.f, 0.95f, 0.38f, // hsl
126.f, 0.97f, 0.75f, // hsv
18.869f, 37.364f, 7.137f // xyz
});
for (const ColorData& color : colors)
{
WHEN("We perform conversion for: " + color.name)
{
THEN("From other color spaces")
{
CompareColor(color.rgb, Nz::Color::FromCMY(color.cyan, color.magenta, color.yellow));
CompareColor(color.rgb, Nz::Color::FromCMYK(color.cyanK, color.magentaK, color.yellowK, color.black));
CompareColor(color.rgb, Nz::Color::FromHSL(color.hue, color.saturation, color.luminosity));
CompareColor(color.rgb, Nz::Color::FromHSV(color.hueV, color.saturationV, color.valueV));
CompareColor(color.rgb, Nz::Color::FromXYZ(Nz::Vector3f(color.x, color.y, color.z)));
}
THEN("To other color spaces")
{
CompareCMY(color.rgb, color.cyan, color.magenta, color.yellow);
CompareCMYK(color.rgb, color.cyanK, color.magentaK, color.yellowK, color.black);
CompareHSL(color.rgb, color.hue, color.saturation, color.luminosity);
CompareHSV(color.rgb, color.hueV, color.saturationV, color.valueV);
CompareXYZ(color.rgb, color.x, color.y, color.z);
}
}
}
}
}

View File

@@ -0,0 +1,29 @@
#include <Nazara/Core/Error.hpp>
#include <catch2/catch_approx.hpp>
#include <catch2/catch_test_macros.hpp>
SCENARIO("Error", "[CORE][ERROR]")
{
Nz::ErrorModeFlags oldFlags = Nz::Error::GetFlags();
GIVEN("Multiple errors")
{
WHEN("Calling to error")
{
THEN("These errors should be written in the log file")
{
Nz::Error::Trigger(Nz::ErrorType::Internal, "ErrorType::Internal");
Nz::Error::Trigger(Nz::ErrorType::Internal, "ErrorType::Internal", 2, "Error.cpp", "2nd place Internal");
REQUIRE("ErrorType::Internal" == Nz::Error::GetLastError());
Nz::Error::Trigger(Nz::ErrorType::Normal, "ErrorType::Normal");
Nz::Error::Trigger(Nz::ErrorType::Normal, "ErrorType::Normal", 2, "Error.cpp", "2nd place Normal");
REQUIRE("ErrorType::Normal" == Nz::Error::GetLastError());
Nz::Error::Trigger(Nz::ErrorType::Warning, "ErrorType::Warning");
Nz::Error::Trigger(Nz::ErrorType::Warning, "ErrorType::Warning", 2, "Error.cpp", "2nd place Warning");
REQUIRE("ErrorType::Warning" == Nz::Error::GetLastError());
}
}
}
Nz::Error::SetFlags(oldFlags);
}

View File

@@ -0,0 +1,84 @@
#include <Nazara/Core/File.hpp>
#include <catch2/catch_approx.hpp>
#include <catch2/catch_test_macros.hpp>
std::filesystem::path GetAssetDir();
SCENARIO("File", "[CORE][FILE]")
{
GIVEN("One file")
{
WHEN("We create a new file")
{
Nz::File file("Test File.txt", Nz::OpenMode_ReadWrite);
REQUIRE(file.GetDirectory() == std::filesystem::current_path());
REQUIRE(file.IsOpen());
THEN("We are allowed to write 3 times 'Test String'")
{
const char message[12] = "Test String"; // 11 + '\0'
Nz::ByteArray byteArray(message, 11);
file.Write("Test String");
file.Write(byteArray);
file.Write(message, 11);
}
AND_THEN("We can retrieve 3 times 'Test String'")
{
char message[12];
REQUIRE(file.Read(message, 11) == 11);
message[11] = '\0';
REQUIRE(std::string(message) == "Test String");
REQUIRE(file.Read(message, 11) == 11);
message[11] = '\0';
REQUIRE(std::string(message) == "Test String");
}
AND_THEN("We can get its size")
{
REQUIRE(file.GetSize() == 33U);
}
AND_THEN("We close it")
{
file.Close();
CHECK(!file.IsOpen());
}
AND_THEN("Change its size")
{
file.SetSize(50U);
REQUIRE(file.GetSize() == 50U);
}
}
WHEN("We delete this file")
{
std::filesystem::remove("Test File.txt");
THEN("It doesn't exist anymore")
{
CHECK(!std::filesystem::exists("Test File.txt"));
}
}
}
GIVEN("The test file")
{
REQUIRE(std::filesystem::exists(GetAssetDir() / "Core/FileTest.txt"));
Nz::File fileTest(GetAssetDir() / "Core/FileTest.txt", Nz::OpenMode::ReadOnly | Nz::OpenMode::Text);
WHEN("We read the first line of the file")
{
REQUIRE(fileTest.IsOpen());
std::string content = fileTest.ReadLine();
THEN("The content must be 'Test'")
{
REQUIRE(content == "Test");
}
}
}
}

View File

@@ -0,0 +1,141 @@
#include <Nazara/Core/HandledObject.hpp>
#include <Nazara/Core/ObjectHandle.hpp>
#include <catch2/catch_approx.hpp>
#include <catch2/catch_test_macros.hpp>
struct ObjectHandle_Test : public Nz::HandledObject<ObjectHandle_Test>
{
ObjectHandle_Test(int value) :
i(value)
{
}
int i;
};
SCENARIO("Handle", "[CORE][HandledObject][ObjectHandle]")
{
GIVEN("One test with two handles")
{
int defaultValue = 1;
ObjectHandle_Test test(defaultValue);
Nz::ObjectHandle<ObjectHandle_Test> handle1 = test.CreateHandle();
Nz::ObjectHandle<ObjectHandle_Test> handle2 = test.CreateHandle();
WHEN("We modify from one")
{
const int newI = 2;
handle1->i = newI;
THEN("The other one should also be modified")
{
REQUIRE(handle2->i == newI);
}
}
WHEN("We copy construct")
{
ObjectHandle_Test other(test);
Nz::ObjectHandle<ObjectHandle_Test> otherHandle = other.CreateHandle();
THEN("Handles should point to 1")
{
CHECK(handle1->i == defaultValue);
CHECK(handle2->i == defaultValue);
CHECK(otherHandle->i == defaultValue);
CHECK(handle2.GetObject() == &test);
CHECK(otherHandle.GetObject() == &other);
}
}
WHEN("We move construct")
{
ObjectHandle_Test other(std::move(test));
Nz::ObjectHandle<ObjectHandle_Test> otherHandle = other.CreateHandle();
THEN("Handles should point to 1")
{
CHECK(handle1->i == defaultValue);
CHECK(handle2->i == defaultValue);
CHECK(otherHandle->i == defaultValue);
CHECK(handle1.GetObject() == &other);
}
}
WHEN("We copy assign")
{
int copyValue = 3;
ObjectHandle_Test other(copyValue);
Nz::ObjectHandle<ObjectHandle_Test> otherHandle = other.CreateHandle();
test = other;
THEN("Handles should point to 3")
{
CHECK(handle1->i == copyValue);
CHECK(handle2->i == copyValue);
CHECK(otherHandle->i == copyValue);
CHECK(handle1.GetObject() == &test);
CHECK(otherHandle.GetObject() == &other);
}
}
WHEN("We move assign")
{
int moveValue = 4;
ObjectHandle_Test other(moveValue);
Nz::ObjectHandle<ObjectHandle_Test> otherHandle = other.CreateHandle();
test = std::move(other);
THEN("Handles to previous objects should be invalid")
{
CHECK_FALSE(handle1.IsValid());
CHECK_FALSE(handle2.IsValid());
}
THEN("Handles should point to 4")
{
CHECK(otherHandle.GetObject() == &test);
CHECK(otherHandle->i == moveValue);
}
}
}
GIVEN("One handle pointing to a default test")
{
ObjectHandle_Test test(1);
Nz::ObjectHandle<ObjectHandle_Test> invalidHandle(&test);
WHEN("We bind it to a HandledObject which is going to die")
{
{
ObjectHandle_Test dyingTest(5);
invalidHandle.Reset(&dyingTest);
}
THEN("It should not be valid")
{
REQUIRE(!invalidHandle.IsValid());
}
}
}
GIVEN("Two handle pointing to two different tests")
{
ObjectHandle_Test test1(1);
Nz::ObjectHandle<ObjectHandle_Test> test1Handle = test1.CreateHandle();
ObjectHandle_Test test2(2);
Nz::ObjectHandle<ObjectHandle_Test> test2Handle = test2.CreateHandle();
WHEN("We swap their content")
{
test1Handle.Swap(test2Handle);
THEN("They should be pointing to the correct one")
{
CHECK(test1Handle.GetObject() == &test2);
CHECK(test2Handle.GetObject() == &test1);
}
}
}
}

View File

@@ -0,0 +1,44 @@
#include <Nazara/Core/ObjectRef.hpp>
#include <catch2/catch_approx.hpp>
#include <catch2/catch_test_macros.hpp>
class Test : public Nz::RefCounted
{
};
SCENARIO("ObjectRef", "[CORE][OBJECTREF]")
{
GIVEN("A ObjectRef")
{
Nz::ObjectRef<Test> objectRef;
WHEN("We have two objectRef handling the same object")
{
Test test;
objectRef = &test;
Nz::ObjectRef<Test> otherRef(&test);
THEN("Pointers the same")
{
REQUIRE(objectRef.IsValid());
REQUIRE(otherRef.IsValid());
}
objectRef.Reset(nullptr);
}
WHEN("We assign it to a simple font")
{
Test test;
THEN("Release suppress the reference to the object")
{
objectRef.Reset(&test);
objectRef.Release();
REQUIRE(!objectRef.IsValid());
}
}
}
}

View File

@@ -0,0 +1,247 @@
#include <Nazara/Core/ParameterList.hpp>
#include <catch2/catch_approx.hpp>
#include <catch2/catch_test_macros.hpp>
void nullAction(void*)
{
}
SCENARIO("ParameterList", "[CORE][PARAMETERLIST]")
{
GIVEN("An empty ParameterList")
{
Nz::ParameterList parameterList;
WHEN("We add Bool 'true' and analogous")
{
bool boolean = true;
parameterList.SetParameter("bool", boolean);
long long intTrue = 1;
parameterList.SetParameter("intTrue", intTrue);
long long intFalse = 0;
parameterList.SetParameter("intFalse", intFalse);
std::string strTrue = "true";
parameterList.SetParameter("strTrue", strTrue);
std::string strFalse = "false";
parameterList.SetParameter("strFalse", strFalse);
THEN("We can get it back")
{
CHECK(parameterList.GetBooleanParameter("bool").GetValue() == true);
}
THEN("Conversion from int to bool should also work if strict mode is disabled")
{
CHECK(parameterList.GetBooleanParameter("intTrue", false).GetValue() == true);
CHECK(parameterList.GetBooleanParameter("intFalse", false).GetValue() == false);
}
THEN("Conversion from str to bool should also work if strict mode is disabled")
{
CHECK(parameterList.GetBooleanParameter("strTrue", false).GetValue() == true);
CHECK(parameterList.GetBooleanParameter("strFalse", false).GetValue() == false);
}
}
WHEN("We add Color 'rgb(1, 2, 3)'")
{
Nz::Color rgb(1, 2, 3);
parameterList.SetParameter("color", rgb);
THEN("We can get it back")
{
CHECK(parameterList.GetColorParameter("color").GetValue() == rgb);
}
}
WHEN("We add Double '3.0' and analogous")
{
double fl = 3.0;
parameterList.SetParameter("double", fl);
long long intDouble = 3;
parameterList.SetParameter("intDouble", intDouble);
std::string strDouble = "3.0";
parameterList.SetParameter("strDouble", strDouble);
THEN("We can get it back")
{
CHECK(parameterList.GetDoubleParameter("double").GetValue() == fl);
}
THEN("Conversion from int to double should also work if strict mode is disabled")
{
CHECK(parameterList.GetDoubleParameter("intDouble", false).GetValue() == fl);
}
THEN("Conversion from string to double should also work if strict mode is disabled")
{
CHECK(parameterList.GetDoubleParameter("strDouble", false).GetValue() == fl);
}
}
WHEN("We add Int '3' and analogous")
{
long long i = 3;
parameterList.SetParameter("int", i);
bool trueInt = 1;
parameterList.SetParameter("trueInt", trueInt);
bool falseInt = 0;
parameterList.SetParameter("falseInt", falseInt);
double doubleInt = 3;
parameterList.SetParameter("doubleInt", doubleInt);
std::string strInt = "3";
parameterList.SetParameter("strInt", strInt);
THEN("We can get it back")
{
CHECK(parameterList.GetIntegerParameter("int").GetValue() == i);
}
THEN("Conversion from bool to int should also work if strict mode is disabled")
{
CHECK(parameterList.GetIntegerParameter("trueInt", false).GetValue() == trueInt);
CHECK(parameterList.GetIntegerParameter("falseInt", false).GetValue() == falseInt);
}
THEN("Conversion from double to int should also work if strict mode is disabled")
{
CHECK(parameterList.GetIntegerParameter("doubleInt", false).GetValue() == i);
}
THEN("Conversion from string to int should also work if strict mode is disabled")
{
CHECK(parameterList.GetIntegerParameter("strInt", false).GetValue() == i);
}
}
WHEN("We add String 'string' and analogous")
{
std::string string("string");
parameterList.SetParameter("string", string);
bool trueString = 1;
parameterList.SetParameter("trueString", trueString);
bool falseString = 0;
parameterList.SetParameter("falseString", falseString);
Nz::Color colorString(1, 2, 3);
parameterList.SetParameter("colorString", colorString);
double doubleString = 3.0;
parameterList.SetParameter("doubleString", doubleString);
long long intString = 3;
parameterList.SetParameter("intString", intString);
THEN("We can get it back")
{
CHECK(parameterList.GetStringParameter("string").GetValue() == string);
CHECK(parameterList.GetStringViewParameter("string").GetValue() == string);
}
THEN("Conversion from bool to str should also work if strict mode is disabled")
{
CHECK(parameterList.GetStringParameter("trueString", false).GetValue() == "true");
CHECK(parameterList.GetStringParameter("falseString", false).GetValue() == "false");
CHECK(parameterList.GetStringViewParameter("trueString", false).GetValue() == "true");
CHECK(parameterList.GetStringViewParameter("falseString", false).GetValue() == "false");
}
THEN("Conversion from color to string should also work if strict mode is disabled")
{
CHECK(parameterList.GetStringParameter("colorString", false).GetValue() == colorString.ToString());
}
THEN("Conversion from string to double should also work if strict mode is disabled")
{
CHECK(parameterList.GetStringParameter("doubleString", false).GetValue() == "3.000000");
}
THEN("Conversion from string to int should also work if strict mode is disabled")
{
CHECK(parameterList.GetStringParameter("intString", false).GetValue() == "3");
}
}
WHEN("We add Pointer to stack value")
{
int stackValue = 3;
void* ptrToStackValue = &stackValue; // Ugly conversion
parameterList.SetParameter("ptr", ptrToStackValue);
THEN("We can get it back")
{
CHECK(parameterList.GetPointerParameter("ptr").GetValue() == ptrToStackValue);
}
}
WHEN("We set our own data")
{
struct Data {
int i;
float f;
};
Data data{ 1, 3.f };
parameterList.SetParameter("userData", &data, nullAction);
THEN("We can get it back")
{
void* ptrToData;
CHECK_NOTHROW(ptrToData = parameterList.GetUserdataParameter("userData").GetValue());
Data* dataPtr = static_cast<Data*>(ptrToData);
CHECK(dataPtr->i == data.i);
CHECK(dataPtr->f == data.f);
}
}
}
GIVEN("A parameter list with some values")
{
Nz::ParameterList parameterList;
long long i = 3;
parameterList.SetParameter("i", i);
double d = 1.0;
parameterList.SetParameter("d", d);
parameterList.SetParameter("toaster");
parameterList.SetParameter("str", "ing");
WHEN("We remove two elements")
{
CHECK(parameterList.HasParameter("i"));
CHECK(parameterList.HasParameter("toaster"));
parameterList.RemoveParameter("i");
parameterList.RemoveParameter("toaster");
THEN("They do not exist anymore")
{
CHECK(!parameterList.HasParameter("i"));
CHECK(!parameterList.HasParameter("toaster"));
}
}
WHEN("We copy this list")
{
Nz::ParameterList copy = parameterList;
THEN("It has the same elements")
{
CHECK(parameterList.HasParameter("i"));
CHECK(parameterList.HasParameter("d"));
CHECK(parameterList.HasParameter("toaster"));
CHECK(parameterList.HasParameter("str"));
}
}
}
}

View File

@@ -0,0 +1,44 @@
#include <Nazara/Core/PrimitiveList.hpp>
#include <catch2/catch_approx.hpp>
#include <catch2/catch_test_macros.hpp>
SCENARIO("PrimitiveList", "[CORE][PRIMITIVELIST]")
{
GIVEN("An empty PrimitiveList")
{
Nz::PrimitiveList primitiveList;
WHEN("We add two primitives")
{
float size = 1.f;
unsigned int subdivision = 1;
Nz::Matrix4f identity = Nz::Matrix4f::Identity();
primitiveList.AddCubicSphere(size, subdivision, identity);
primitiveList.AddBox(Nz::Vector3f(size), Nz::Vector3ui(subdivision), identity);
primitiveList.AddIcoSphere(size, subdivision, identity);
THEN("There must be two items")
{
REQUIRE(primitiveList.GetSize() == 3);
}
THEN("The first one is the cubic sphere")
{
REQUIRE(primitiveList(0).type == Nz::PrimitiveType::Sphere);
REQUIRE(primitiveList(0).sphere.type == Nz::SphereType::Cubic);
}
THEN("The second one is the box")
{
REQUIRE(primitiveList(1).type == Nz::PrimitiveType::Box);
}
THEN("The third one is the ico sphere")
{
REQUIRE(primitiveList(2).type == Nz::PrimitiveType::Sphere);
REQUIRE(primitiveList(2).sphere.type == Nz::SphereType::Ico);
}
}
}
}

View File

@@ -0,0 +1,30 @@
#include <Nazara/Core/RefCounted.hpp>
#include <catch2/catch_approx.hpp>
#include <catch2/catch_test_macros.hpp>
SCENARIO("RefCounted", "[CORE][REFCOUNTED]")
{
GIVEN("A refcounted persistent")
{
Nz::RefCounted refCounted;
REQUIRE(refCounted.IsPersistent() == true);
WHEN("We add a reference to this persistent object")
{
THEN("Number of references should be one")
{
refCounted.AddReference();
REQUIRE(refCounted.GetReferenceCount() == 1);
REQUIRE(refCounted.RemoveReference() == false);
}
AND_THEN("We suppress the reference, object is still alive")
{
refCounted.AddReference();
REQUIRE(refCounted.IsPersistent());
REQUIRE(refCounted.RemoveReference() == false);
REQUIRE(refCounted.GetReferenceCount() == 0);
}
}
}
}

View File

@@ -0,0 +1,263 @@
#include <Nazara/Core/SerializationContext.hpp>
#include <Nazara/Core/Color.hpp>
#include <Nazara/Core/MemoryView.hpp>
#include <Nazara/Math/BoundingVolume.hpp>
#include <Nazara/Math/Frustum.hpp>
#include <Nazara/Math/Ray.hpp>
#include <array>
#include <catch2/catch_approx.hpp>
#include <catch2/catch_test_macros.hpp>
SCENARIO("Serialization", "[CORE][SERIALIZATION]")
{
GIVEN("A context of serialization")
{
std::array<char, 256> datas; // The array must be bigger than any of the serializable classes
Nz::MemoryView stream(datas.data(), datas.size());
Nz::SerializationContext context;
context.stream = &stream;
WHEN("We serialize basic types")
{
THEN("Arithmetical types")
{
context.stream->SetCursorPos(0);
REQUIRE(Serialize(context, 3));
int value = 0;
context.stream->SetCursorPos(0);
REQUIRE(Unserialize(context, &value));
REQUIRE(value == 3);
}
THEN("Boolean type")
{
context.stream->SetCursorPos(0);
REQUIRE(Serialize(context, true));
context.FlushBits(); //< Don't forget to flush bits (it is NOT done by the stream)
context.stream->SetCursorPos(0);
bool value = false;
REQUIRE(Unserialize(context, &value));
REQUIRE(value == true);
}
}
WHEN("We serialize mathematical classes")
{
THEN("BoudingVolume")
{
context.stream->SetCursorPos(0);
Nz::BoundingVolumef nullVolume = Nz::BoundingVolumef::Null();
Nz::BoundingVolumef copy(nullVolume);
REQUIRE(Serialize(context, nullVolume));
nullVolume = Nz::BoundingVolumef::Infinite();
REQUIRE(nullVolume != copy);
context.stream->SetCursorPos(0);
REQUIRE(Unserialize(context, &nullVolume));
REQUIRE(nullVolume == copy);
}
THEN("Box")
{
context.stream->SetCursorPos(0);
Nz::Boxf zeroBox = Nz::Boxf::Zero();
Nz::Boxf copy(zeroBox);
REQUIRE(Serialize(context, zeroBox));
zeroBox = Nz::Boxf(1, 1, 1, 1, 1, 1);
REQUIRE(zeroBox != copy);
context.stream->SetCursorPos(0);
REQUIRE(Unserialize(context, &zeroBox));
REQUIRE(zeroBox == copy);
}
THEN("EulerAngles")
{
context.stream->SetCursorPos(0);
Nz::EulerAnglesf zeroEuler = Nz::EulerAnglesf::Zero();
Nz::EulerAnglesf copy(zeroEuler);
REQUIRE(Serialize(context, zeroEuler));
zeroEuler = Nz::EulerAnglesf(10, 24, 6); // Random values
REQUIRE(zeroEuler != copy);
context.stream->SetCursorPos(0);
REQUIRE(Unserialize(context, &zeroEuler));
REQUIRE(zeroEuler == copy);
}
THEN("Frustum")
{
context.stream->SetCursorPos(0);
Nz::Frustumf frustum = Nz::Frustumf::Build(10, 10, 10, 100, Nz::Vector3f::UnitX(), Nz::Vector3f::UnitZ()); // Random values
Nz::Frustumf copy(frustum);
REQUIRE(Serialize(context, frustum));
frustum = Nz::Frustumf::Build(50, 40, 20, 100, Nz::Vector3f::UnitX(), Nz::Vector3f::UnitZ());
for (std::size_t i = 0; i < Nz::FrustumPlaneCount; ++i)
REQUIRE(frustum.GetPlane(static_cast<Nz::FrustumPlane>(i)) != copy.GetPlane(static_cast<Nz::FrustumPlane>(i)));
context.stream->SetCursorPos(0);
REQUIRE(Unserialize(context, &frustum));
for (std::size_t i = 0; i < Nz::FrustumPlaneCount; ++i)
REQUIRE(frustum.GetPlane(static_cast<Nz::FrustumPlane>(i)) == copy.GetPlane(static_cast<Nz::FrustumPlane>(i)));
}
THEN("Matrix4")
{
context.stream->SetCursorPos(0);
Nz::Matrix4f zeroMatrix = Nz::Matrix4f::Zero();
Nz::Matrix4f copy(zeroMatrix);
REQUIRE(Serialize(context, zeroMatrix));
zeroMatrix = Nz::Matrix4f::Identity(); // Random values
REQUIRE(zeroMatrix != copy);
context.stream->SetCursorPos(0);
REQUIRE(Unserialize(context, &zeroMatrix));
REQUIRE(zeroMatrix == copy);
}
THEN("OrientedBox")
{
context.stream->SetCursorPos(0);
Nz::OrientedBoxf zeroOBB = Nz::OrientedBoxf::Zero();
Nz::OrientedBoxf copy(zeroOBB);
REQUIRE(Serialize(context, zeroOBB));
zeroOBB = Nz::OrientedBoxf(Nz::Boxf(1, 1, 1, 1, 1, 1)); // Random values
REQUIRE(zeroOBB != copy);
context.stream->SetCursorPos(0);
REQUIRE(Unserialize(context, &zeroOBB));
REQUIRE(zeroOBB == copy);
}
THEN("Plane")
{
context.stream->SetCursorPos(0);
Nz::Planef planeXY = Nz::Planef::XY();
Nz::Planef copy(planeXY);
REQUIRE(Serialize(context, planeXY));
planeXY = Nz::Planef::YZ();
REQUIRE(planeXY != copy);
context.stream->SetCursorPos(0);
REQUIRE(Unserialize(context, &planeXY));
REQUIRE(planeXY == copy);
}
THEN("Quaternion")
{
context.stream->SetCursorPos(0);
Nz::Quaternionf quaternionIdentity = Nz::Quaternionf::Identity();
Nz::Quaternionf copy(quaternionIdentity);
REQUIRE(Serialize(context, quaternionIdentity));
quaternionIdentity = Nz::Quaternionf::Zero();
REQUIRE(quaternionIdentity != copy);
context.stream->SetCursorPos(0);
REQUIRE(Unserialize(context, &quaternionIdentity));
REQUIRE(quaternionIdentity == copy);
}
THEN("Ray")
{
context.stream->SetCursorPos(0);
Nz::Rayf axisX = Nz::Rayf::AxisX();
Nz::Rayf copy(axisX);
REQUIRE(Serialize(context, axisX));
axisX = Nz::Rayf::AxisY();
REQUIRE(axisX != copy);
context.stream->SetCursorPos(0);
REQUIRE(Unserialize(context, &axisX));
REQUIRE(axisX == copy);
}
THEN("Rect")
{
context.stream->SetCursorPos(0);
Nz::Rectf zeroRect = Nz::Rectf::Zero();
Nz::Rectf copy(zeroRect);
REQUIRE(Serialize(context, zeroRect));
zeroRect = Nz::Rectf(1, 1, 1, 1); // Random values
REQUIRE(zeroRect != copy);
context.stream->SetCursorPos(0);
REQUIRE(Unserialize(context, &zeroRect));
REQUIRE(zeroRect == copy);
}
THEN("Sphere")
{
context.stream->SetCursorPos(0);
Nz::Spheref zeroSphere = Nz::Spheref::Zero();
Nz::Spheref copy(zeroSphere);
REQUIRE(Serialize(context, zeroSphere));
zeroSphere = Nz::Spheref::Unit();
REQUIRE(zeroSphere != copy);
context.stream->SetCursorPos(0);
REQUIRE(Unserialize(context, &zeroSphere));
REQUIRE(zeroSphere == copy);
}
THEN("Vector2")
{
context.stream->SetCursorPos(0);
Nz::Vector2f unitX = Nz::Vector2f::UnitX();
Nz::Vector2f copy(unitX);
REQUIRE(Serialize(context, unitX));
unitX = Nz::Vector2f::UnitY();
REQUIRE(unitX != copy);
context.stream->SetCursorPos(0);
REQUIRE(Unserialize(context, &unitX));
REQUIRE(unitX == copy);
}
THEN("Vector3")
{
context.stream->SetCursorPos(0);
Nz::Vector3f unitX = Nz::Vector3f::UnitX();
Nz::Vector3f copy(unitX);
REQUIRE(Serialize(context, unitX));
unitX = Nz::Vector3f::UnitY();
REQUIRE(unitX != copy);
context.stream->SetCursorPos(0);
REQUIRE(Unserialize(context, &unitX));
REQUIRE(unitX == copy);
}
THEN("Vector4")
{
context.stream->SetCursorPos(0);
Nz::Vector4f unitX = Nz::Vector4f::UnitX();
Nz::Vector4f copy(unitX);
REQUIRE(Serialize(context, unitX));
unitX = Nz::Vector4f::UnitY();
REQUIRE(unitX != copy);
context.stream->SetCursorPos(0);
REQUIRE(Unserialize(context, &unitX));
REQUIRE(unitX == copy);
}
}
WHEN("We serialize core classes")
{
THEN("Color")
{
context.stream->SetCursorPos(0);
Nz::Color red = Nz::Color::Red;
Nz::Color copy(red);
REQUIRE(Serialize(context, red));
red = Nz::Color::Black;
REQUIRE(red != copy);
context.stream->SetCursorPos(0);
REQUIRE(Unserialize(context, &red));
REQUIRE(red == copy);
}
THEN("String")
{
context.stream->SetCursorPos(0);
std::string string = "string";
std::string copy(string);
REQUIRE(Serialize(context, string));
string = "another";
REQUIRE(string != copy);
context.stream->SetCursorPos(0);
REQUIRE(Unserialize(context, &string));
REQUIRE(string == copy);
}
}
}
}

View File

@@ -0,0 +1,154 @@
#include <Nazara/Core/StringExt.hpp>
#include <catch2/catch_approx.hpp>
#include <catch2/catch_test_macros.hpp>
SCENARIO("String", "[CORE][STRING]")
{
std::string unicodeString(u8"\u00E0\u00E9\u00E7\u0153\u00C2\u5B98\u46E1");
WHEN("Checking if string ends with")
{
CHECK(Nz::EndsWith("", ""));
CHECK(Nz::EndsWith("Nazara Engine", ""));
CHECK(Nz::EndsWith("Nazara Engine", "Engine"));
CHECK_FALSE(Nz::EndsWith("Nazara Engine", "engine"));
CHECK_FALSE(Nz::EndsWith("Nazara Engine", " ngine"));
CHECK_FALSE(Nz::EndsWith("Nazara Engine", "NazaraEngine"));
CHECK_FALSE(Nz::EndsWith("Nazara Engine", "Nazara"));
CHECK_FALSE(Nz::EndsWith("Nazara Engine", "Sir Nazara van Engine"));
CHECK(Nz::EndsWith("", "", Nz::CaseIndependent{}));
CHECK(Nz::EndsWith("Nazara Engine", "", Nz::CaseIndependent{}));
CHECK(Nz::EndsWith("Nazara Engine", "Engine", Nz::CaseIndependent{}));
CHECK(Nz::EndsWith("Nazara Engine", "engine", Nz::CaseIndependent{}));
CHECK(Nz::EndsWith("Nazara engine", "EnGiNe", Nz::CaseIndependent{}));
CHECK_FALSE(Nz::EndsWith("Nazara Engine", " ngine", Nz::CaseIndependent{}));
CHECK_FALSE(Nz::EndsWith("Nazara Engine", "NazaraEngine", Nz::CaseIndependent{}));
CHECK_FALSE(Nz::EndsWith("Nazara Engine", "Nazara", Nz::CaseIndependent{}));
CHECK_FALSE(Nz::EndsWith("Nazara Engine", "Sir Nazara van Engine", Nz::CaseIndependent{}));
CHECK(Nz::EndsWith(u8"L'\u00CEle de R\u00E9", u8"", Nz::UnicodeAware{}));
CHECK(Nz::EndsWith(u8"L'\u00CEle de R\u00E9", u8"R\u00E9", Nz::UnicodeAware{}));
CHECK_FALSE(Nz::EndsWith(u8"L'\u00CEle de R\u00E9", u8"Long long j\u00F4hnson", Nz::UnicodeAware{}));
CHECK(Nz::EndsWith(u8"L'\u00CEle de R\u00E9", u8"", Nz::CaseIndependent{}, Nz::UnicodeAware{}));
CHECK(Nz::EndsWith(u8"L'\u00CEle de R\u00E9", u8"R\u00C9", Nz::CaseIndependent{}, Nz::UnicodeAware{}));
CHECK_FALSE(Nz::EndsWith(u8"L'\u00CEle de R\u00E9", u8"Long long j\u00F4hnson", Nz::CaseIndependent{}, Nz::UnicodeAware{}));
}
WHEN("Converting string back and forth")
{
CHECK(Nz::FromUtf16String(Nz::ToUtf16String(unicodeString)) == unicodeString);
CHECK(Nz::FromUtf32String(Nz::ToUtf32String(unicodeString)) == unicodeString);
}
WHEN("Fetching words")
{
CHECK(Nz::GetWord({}, 0).empty());
CHECK(Nz::GetWord(" ", 0).empty());
std::string sentence = u8"\nSay hello\tto Nazara\u00A0Engine\n\t! "; //< \u00A0 is a No-Break Space
CHECK(Nz::GetWord(sentence, 0) == "Say");
CHECK(Nz::GetWord(sentence, 1) == "hello");
CHECK(Nz::GetWord(sentence, 2) == "to");
CHECK(Nz::GetWord(sentence, 3) == u8"Nazara\u00A0Engine");
CHECK(Nz::GetWord(sentence, 4) == "!");
CHECK(Nz::GetWord(sentence, 5).empty());
// Try the same using Unicode aware overloads
CHECK(Nz::GetWord(sentence, 0, Nz::UnicodeAware{}) == "Say");
CHECK(Nz::GetWord(sentence, 1, Nz::UnicodeAware{}) == "hello");
CHECK(Nz::GetWord(sentence, 2, Nz::UnicodeAware{}) == "to");
CHECK(Nz::GetWord(sentence, 3, Nz::UnicodeAware{}) == "Nazara");
CHECK(Nz::GetWord(sentence, 4, Nz::UnicodeAware{}) == "Engine");
CHECK(Nz::GetWord(sentence, 5, Nz::UnicodeAware{}) == "!");
CHECK(Nz::GetWord(sentence, 6, Nz::UnicodeAware{}).empty());
}
WHEN("Checking if string is number")
{
CHECK(Nz::IsNumber("123456"));
CHECK(Nz::IsNumber("-123456"));
CHECK_FALSE(Nz::IsNumber("123 "));
CHECK_FALSE(Nz::IsNumber("Nazara Engine"));
CHECK_FALSE(Nz::IsNumber("12345Nazara Engine"));
}
WHEN("Matching patterns")
{
CHECK(Nz::MatchPattern("Lynix", "?????"));
CHECK(Nz::MatchPattern("Lynix", "*Lynix"));
CHECK(Nz::MatchPattern("Lynox", "*Lyn?x"));
CHECK_FALSE(Nz::MatchPattern("", "?"));
CHECK_FALSE(Nz::MatchPattern("", "*"));
const char* pattern = "Naz?ra *gine";
CHECK(Nz::MatchPattern("Nazara Engine", pattern));
CHECK(Nz::MatchPattern("Nazora engine", pattern));
CHECK(Nz::MatchPattern("Nazora Biggine", pattern));
CHECK(Nz::MatchPattern("Nazora gine", pattern));
CHECK_FALSE(Nz::MatchPattern("Nazaragine", pattern));
CHECK_FALSE(Nz::MatchPattern("Nazorra Engine", pattern));
CHECK_FALSE(Nz::MatchPattern("Nazra Engine", pattern));
CHECK_FALSE(Nz::MatchPattern("NazaraEngine", pattern));
}
WHEN("Converting pointers to string")
{
CHECK(Nz::TrimRight(Nz::PointerToString(nullptr), '0') == "0x");
const void* ptr = reinterpret_cast<const void*>(static_cast<std::uintptr_t>(0xDEADBEEF));
CHECK(Nz::MatchPattern(Nz::PointerToString(ptr), "0x*DEADBEEF"));
}
WHEN("Replacing strings")
{
std::string str = "Nazara Engine";
REQUIRE(Nz::ReplaceStr(str, "Nazara", "Unreal") == "Unreal Engine");
REQUIRE(Nz::ReplaceStr(str, "Engine", "Reality") == "Unreal Reality");
REQUIRE(Nz::ReplaceStr(str, "Unreal Reality", "Ungine") == "Ungine");
}
WHEN("Checking if string starts with")
{
CHECK(Nz::StartsWith("Nazara Engine", ""));
CHECK(Nz::StartsWith("Nazara Engine", "Nazara"));
CHECK_FALSE(Nz::StartsWith("Nazara Engine", "Navara"));
CHECK_FALSE(Nz::StartsWith("Nazara Engine", "NaZaRa"));
CHECK_FALSE(Nz::StartsWith("Nazara Engine", "Long long johnson"));
CHECK(Nz::StartsWith("NAZARA Engine", "", Nz::CaseIndependent{}));
CHECK(Nz::StartsWith("NAZARA Engine", "Nazara", Nz::CaseIndependent{}));
CHECK(Nz::StartsWith("NAZARA Engine", "NaZaRa", Nz::CaseIndependent{}));
CHECK_FALSE(Nz::StartsWith("NAZARA Engine", "NavaRa", Nz::CaseIndependent{}));
CHECK_FALSE(Nz::StartsWith("NAZARA Engine", "Long long johnson", Nz::CaseIndependent{}));
CHECK(Nz::StartsWith(u8"L'\u00CEle de R\u00E9", u8"", Nz::UnicodeAware{}));
CHECK(Nz::StartsWith(u8"L'\u00CEle de R\u00E9", u8"L'\u00CEle", Nz::UnicodeAware{}));
CHECK_FALSE(Nz::StartsWith(u8"L'\u00CEle de R\u00E9", u8"Long long j\u00F4hnson", Nz::UnicodeAware{}));
CHECK(Nz::StartsWith(u8"L'\u00CEle de R\u00E9", u8"", Nz::CaseIndependent{}, Nz::UnicodeAware{}));
CHECK(Nz::StartsWith(u8"L'\u00CEle de R\u00E9", u8"l'\u00EEle", Nz::CaseIndependent{}, Nz::UnicodeAware{}));
CHECK_FALSE(Nz::StartsWith(u8"L'\u00CEle de R\u00E9", u8"Long long j\u00F4hnson", Nz::CaseIndependent{}, Nz::UnicodeAware{}));
}
WHEN("Converting between lower and upper")
{
CHECK(Nz::ToLower("Nazara Engine") == "nazara engine");
CHECK(Nz::ToLower(u8"L'\u00CELE DE R\u00C9", Nz::UnicodeAware{}) == u8"l'\u00EEle de r\u00E9");
CHECK(Nz::ToUpper("Nazara Engine") == "NAZARA ENGINE");
CHECK(Nz::ToUpper(u8"l'\u00EEle de r\u00E9", Nz::UnicodeAware{}) == u8"L'\u00CELE DE R\u00C9");
}
WHEN("Trimming strings")
{
CHECK(Nz::Trim(" \n Hello World\t") == "Hello World");
CHECK(Nz::Trim("Nazara Engin", 'n') == "Nazara Engi");
CHECK(Nz::Trim("Nazara Engin", 'n', Nz::CaseIndependent{}) == "azara Engi");
CHECK(Nz::Trim(unicodeString, Nz::UnicodeAware{}) == unicodeString);
CHECK(Nz::Trim("\n\t" + unicodeString + "\t ", Nz::UnicodeAware{}) == unicodeString);
CHECK(Nz::Trim(unicodeString, U'\u46E1', Nz::UnicodeAware{}) == u8"\u00E0\u00E9\u00E7\u0153\u00C2\u5B98");
CHECK(Nz::Trim(unicodeString, Nz::Unicode::Category_Letter, Nz::UnicodeAware{}) == "");
}
}

View File

@@ -0,0 +1,85 @@
#include <Nazara/Core/Uuid.hpp>
#include <catch2/catch_approx.hpp>
#include <catch2/catch_test_macros.hpp>
#include <regex>
#include <set>
#include <unordered_set>
SCENARIO("Uuid", "[CORE][UUID]")
{
WHEN("Parsing UUID")
{
CHECK(Nz::Uuid::FromString("00000000-0000-0000-0000-000000000000") == Nz::Uuid());
CHECK(Nz::Uuid::FromString("1b0e29af-fd4a-43e0-ba4c-e9334183b1f1") == Nz::Uuid({ 0x1B, 0x0E, 0x29, 0xAF, 0xFD, 0x4A, 0x43, 0xE0, 0xBA, 0x4C, 0xE9, 0x33, 0x41, 0x83, 0xB1, 0xF1 }));
// Testing some invalid cases
CHECK(Nz::Uuid::FromString("Nazara Engine") == Nz::Uuid());
CHECK(Nz::Uuid::FromString("1b0e29af_fd4a_43e0_ba4c_e9334183b1f1") == Nz::Uuid());
CHECK(Nz::Uuid::FromString("1b0e29af-fd4a_43e0_ba4c_e93341-3b1f1") == Nz::Uuid());
CHECK(Nz::Uuid::FromString("Zb0e29af-fd4a-43e0-ba4c-e9334183b1f1") == Nz::Uuid());
CHECK(Nz::Uuid::FromString("1b0e29a\t-fd4a-43e0-ba4c-e9334183b1f1") == Nz::Uuid());
CHECK(Nz::Uuid::FromString("1b0e29af-fd4\v-43e0-ba4c-e9334183b1f1") == Nz::Uuid());
CHECK(Nz::Uuid::FromString("1b0e29af-fd4a-\r3e0-ba4c-e9334183b1f1") == Nz::Uuid());
CHECK(Nz::Uuid::FromString("1b0e29af-fd4a-43e\n-ba4c-e9334183b1f1") == Nz::Uuid());
CHECK(Nz::Uuid::FromString("1b0e29af-fd4a-43e0-\0a4c-e9334183b1f1") == Nz::Uuid());
CHECK(Nz::Uuid::FromString("1b0e29af-fd4a-43e0-ba4\n-e9334183b1f1") == Nz::Uuid());
CHECK(Nz::Uuid::FromString("1b0e29af-fd4a-43e0-ba4c-g9334183b1f1") == Nz::Uuid());
CHECK(Nz::Uuid::FromString("1b0e29af-fd4a-43e0-ba4c-e9334183b1fG") == Nz::Uuid());
CHECK(Nz::Uuid::FromString("1b0e29af-HELL-OWOR-LDDD-e9334183b1f1") == Nz::Uuid());
}
WHEN("Generating a null UUID")
{
Nz::Uuid nullUuid;
CHECK(nullUuid.IsNull());
CHECK(nullUuid.ToArray() == std::array<Nz::UInt8, 16>{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0});
CHECK(nullUuid.ToString() == "00000000-0000-0000-0000-000000000000");
CHECK(nullUuid.ToStringArray() == std::array<char, 37>{"00000000-0000-0000-0000-000000000000"});
CHECK(nullUuid == Nz::Uuid{});
CHECK(nullUuid >= Nz::Uuid{});
CHECK(nullUuid <= Nz::Uuid{});
CHECK_FALSE(nullUuid > Nz::Uuid{});
CHECK_FALSE(nullUuid < Nz::Uuid{});
CHECK(nullUuid != Nz::Uuid::Generate());
CHECK(Nz::Uuid::FromString(nullUuid.ToString()) == nullUuid);
}
WHEN("Generating a UUID")
{
// https://stackoverflow.com/questions/136505/searching-for-uuids-in-text-with-regex
std::regex uuidRegex(R"(^\b[0-9a-f]{8}\b-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-\b[0-9a-f]{12}\b$)");
Nz::Uuid uuid = Nz::Uuid::Generate();
INFO(uuid);
CHECK_FALSE(uuid.IsNull());
CHECK(std::regex_match(uuid.ToString(), uuidRegex));
CHECK(uuid == uuid);
CHECK(uuid == Nz::Uuid{uuid.ToArray()});
CHECK(uuid >= uuid);
CHECK(uuid <= uuid);
CHECK_FALSE(uuid > uuid);
CHECK_FALSE(uuid < uuid);
CHECK(Nz::Uuid::FromString(uuid.ToString()) == uuid);
}
WHEN("Generating multiple UUID, they are unique")
{
std::set<Nz::Uuid> uuidSet;
std::unordered_set<Nz::Uuid> uuidUnorderedset;
auto InsertUniqueUuid = [](auto& container, const Nz::Uuid& uuid)
{
auto it = container.find(uuid);
REQUIRE(it == container.end());
container.insert(uuid);
};
for (std::size_t i = 0; i < 1'000; ++i)
{
auto uuid = Nz::Uuid::Generate();
InsertUniqueUuid(uuidSet, uuid);
InsertUniqueUuid(uuidUnorderedset, uuid);
}
}
}

View File

@@ -0,0 +1,322 @@
#include <Nazara/Core/Algorithm.hpp>
#include <Nazara/Core/File.hpp>
#include <Nazara/Core/StringExt.hpp>
#include <Nazara/Core/VirtualDirectory.hpp>
#include <Nazara/Core/Hash/SHA256.hpp>
#include <catch2/catch_approx.hpp>
#include <catch2/catch_test_macros.hpp>
#include <random>
std::filesystem::path GetAssetDir();
TEST_CASE("VirtualDirectory", "[Core][VirtualDirectory]")
{
SECTION("Creating a virtual directory")
{
std::shared_ptr<Nz::VirtualDirectory> virtualDir = std::make_shared<Nz::VirtualDirectory>();
WHEN("Iterating it, it only has . and ..")
{
bool dot = false;
bool dotDot = false;
virtualDir->Foreach([&](std::string_view name, const Nz::VirtualDirectory::Entry& /*entry*/)
{
if (name == ".")
{
CHECK_FALSE(dot);
dot = true;
}
else if (name == "..")
{
CHECK_FALSE(dotDot);
dotDot = true;
}
if (name != "." && name != "..")
FAIL("Got file " << name);
}, true);
CHECK(dot);
CHECK(dotDot);
}
AND_WHEN("Iterating it without dots, directory it empty")
{
virtualDir->Foreach([&](std::string_view name, const Nz::VirtualDirectory::Entry& /*entry*/)
{
FAIL("There should be nothing here, got " << name);
});
}
AND_WHEN("We try to retrieve a non-existing file, it fails")
{
CHECK_FALSE(virtualDir->GetEntry("File.bin", [](const Nz::VirtualDirectory::Entry& /*entry*/)
{
return true;
}));
CHECK_FALSE(virtualDir->GetEntry("Foo/File.bin", [](const Nz::VirtualDirectory::Entry& /*entry*/)
{
return true;
}));
CHECK_FALSE(virtualDir->GetEntry("Foo/Bar/File.bin", [](const Nz::VirtualDirectory::Entry& /*entry*/)
{
return true;
}));
virtualDir->StoreDirectory("Foo/Bar", std::make_shared<Nz::VirtualDirectory>());
CHECK(virtualDir->GetEntry("Foo", [](const Nz::VirtualDirectory::Entry& entry)
{
return std::holds_alternative<Nz::VirtualDirectory::VirtualDirectoryEntry>(entry);
}));
CHECK(virtualDir->GetEntry("Foo/Bar", [](const Nz::VirtualDirectory::Entry& entry)
{
return std::holds_alternative<Nz::VirtualDirectory::VirtualDirectoryEntry>(entry);
}));
CHECK_FALSE(virtualDir->GetEntry("Foo/Bar/File.bin", [](const Nz::VirtualDirectory::Entry& /*entry*/)
{
return true;
}));
}
std::mt19937 randGen(std::random_device{}());
auto GenerateRandomData = [&]
{
std::vector<Nz::UInt8> randomData;
for (std::size_t i = 0; i < 256; ++i)
{
unsigned int data = randGen();
randomData.push_back(Nz::SafeCast<Nz::UInt8>((data & 0x000000FF) >> 0));
randomData.push_back(Nz::SafeCast<Nz::UInt8>((data & 0x0000FF00) >> 8));
randomData.push_back(Nz::SafeCast<Nz::UInt8>((data & 0x00FF0000) >> 16));
randomData.push_back(Nz::SafeCast<Nz::UInt8>((data & 0xFF000000) >> 24));
}
return randomData;
};
auto CheckFile = [&](std::string_view path, const std::vector<Nz::UInt8>& expectedData)
{
return virtualDir->GetEntry(path, [&](const Nz::VirtualDirectory::Entry& entry)
{
if (!std::holds_alternative<Nz::VirtualDirectory::FileContentEntry>(entry))
{
FAIL("Target is not a file");
return false;
}
const auto& contentEntry = std::get<Nz::VirtualDirectory::FileContentEntry>(entry);
return std::equal(expectedData.begin(), expectedData.end(), contentEntry.data.begin(), contentEntry.data.end());
});
};
auto CheckFileContent = [&](std::string_view path, const std::vector<Nz::UInt8>& expectedData)
{
return virtualDir->GetFileContent(path, [&](const void* data, std::size_t size)
{
if (expectedData.size() != size)
{
FAIL("size doesn't match");
return false;
}
return std::memcmp(expectedData.data(), data, expectedData.size()) == 0;
});
};
WHEN("Storing a file")
{
auto randomData = GenerateRandomData();
virtualDir->StoreFile("File.bin", randomData);
WHEN("We retrieve it")
{
CHECK(CheckFile("File.bin", randomData));
CHECK(CheckFileContent("File.bin", randomData));
}
}
WHEN("Storing multiples files")
{
std::array paths = {
"Abc",
"Ab/cd",
"po/mme\\de/terre.o",
"Nazara",
"Engine.exe",
"Un/Deux/Trois",
"Gnocchi.fromage",
"Karmeliet.triple",
"Mogwai.gremlins"
};
struct File
{
std::string path;
std::vector<Nz::UInt8> data;
};
std::vector<File> files;
std::unordered_map<std::string, std::size_t> filePathToIndex;
for (const char* path : paths)
{
auto& file = files.emplace_back();
file.data = GenerateRandomData();
file.path = path;
filePathToIndex[file.path] = files.size() - 1;
}
// Insert files into the virtual directory
for (const File& file : files)
{
INFO("Storing " << file.path);
CHECK_NOTHROW(virtualDir->StoreFile(file.path, file.data));
}
// Try to retrieve them
for (const File& file : files)
{
INFO("Retrieving " << file.path);
CHECK(CheckFile(file.path, file.data));
INFO("Retrieving " << file.path << " using GetFileContent");
CHECK(CheckFileContent(file.path, file.data));
}
}
}
SECTION("Accessing filesystem using a VirtualDirectory")
{
std::shared_ptr<Nz::VirtualDirectory> resourceDir = std::make_shared<Nz::VirtualDirectory>(GetAssetDir());
WHEN("Iterating, it's not empty")
{
bool empty = true;
resourceDir->Foreach([&](std::string_view name, const Nz::VirtualDirectory::Entry& entry)
{
CHECK_FALSE(name == ".");
CHECK_FALSE(name == "..");
INFO("Only physical files and directories are expected");
CHECK((std::holds_alternative<Nz::VirtualDirectory::PhysicalDirectoryEntry>(entry) || std::holds_alternative<Nz::VirtualDirectory::PhysicalFileEntry>(entry)));
empty = false;
});
REQUIRE_FALSE(empty);
}
auto CheckFileHash = [](const Nz::VirtualDirectoryPtr& dir, const char* filepath, const char* expectedHash)
{
return dir->GetEntry(filepath, [&](const Nz::VirtualDirectory::Entry& entry)
{
REQUIRE(std::holds_alternative<Nz::VirtualDirectory::PhysicalFileEntry>(entry));
const auto& physFileEntry = std::get<Nz::VirtualDirectory::PhysicalFileEntry>(entry);
Nz::File file(physFileEntry.filePath);
Nz::SHA256Hash hash;
WHEN("We compute " << hash.GetHashName() << " of " << physFileEntry.filePath << " file")
{
CHECK(Nz::ToUpper(Nz::ComputeHash(hash, file).ToHex()) == expectedHash);
}
return true;
});
};
auto CheckFileContentHash = [](const Nz::VirtualDirectoryPtr& dir, const char* filepath, const char* expectedHash)
{
return dir->GetFileContent(filepath, [&](const void* data, std::size_t size)
{
Nz::SHA256Hash hash;
WHEN("We compute " << hash.GetHashName() << " of " << filepath << " file")
{
hash.Begin();
hash.Append(static_cast<const Nz::UInt8*>(data), size);
CHECK(Nz::ToUpper(hash.End().ToHex()) == expectedHash);
}
});
};
WHEN("Accessing files")
{
CHECK(CheckFileHash(resourceDir, "Logo.png", "5C4B9387327C039A6CE9ED51983D6C2ADA9F9DD01D024C2D5D588237ADFC7423"));
CHECK(CheckFileHash(resourceDir, "./Logo.png", "5C4B9387327C039A6CE9ED51983D6C2ADA9F9DD01D024C2D5D588237ADFC7423"));
CHECK(CheckFileHash(resourceDir, "Audio/The_Brabanconne.ogg", "E07706E0BEEC7770CDE36008826743AF9EEE5C80CA0BD83C37771CBC8B52E738"));
CHECK(CheckFileHash(resourceDir, "Audio/./The_Brabanconne.ogg", "E07706E0BEEC7770CDE36008826743AF9EEE5C80CA0BD83C37771CBC8B52E738"));
CHECK_FALSE(CheckFileHash(resourceDir, "The_Brabanconne.ogg", "E07706E0BEEC7770CDE36008826743AF9EEE5C80CA0BD83C37771CBC8B52E738"));
CHECK_FALSE(CheckFileHash(resourceDir, "The_Brabanconne.ogg", "E07706E0BEEC7770CDE36008826743AF9EEE5C80CA0BD83C37771CBC8B52E738"));
CHECK_FALSE(CheckFileHash(resourceDir, "Audio/../The_Brabanconne.ogg", "E07706E0BEEC7770CDE36008826743AF9EEE5C80CA0BD83C37771CBC8B52E738"));
// We can't escape the virtual directory
CHECK(CheckFileHash(resourceDir, "../Logo.png", "5C4B9387327C039A6CE9ED51983D6C2ADA9F9DD01D024C2D5D588237ADFC7423"));
CHECK(CheckFileHash(resourceDir, "../../Logo.png", "5C4B9387327C039A6CE9ED51983D6C2ADA9F9DD01D024C2D5D588237ADFC7423"));
CHECK(CheckFileHash(resourceDir, "../../Audio/The_Brabanconne.ogg", "E07706E0BEEC7770CDE36008826743AF9EEE5C80CA0BD83C37771CBC8B52E738"));
CHECK(CheckFileHash(resourceDir, ".././Audio/The_Brabanconne.ogg", "E07706E0BEEC7770CDE36008826743AF9EEE5C80CA0BD83C37771CBC8B52E738"));
CHECK_FALSE(CheckFileHash(resourceDir, "../Tests/Audio/The_Brabanconne.ogg", "E07706E0BEEC7770CDE36008826743AF9EEE5C80CA0BD83C37771CBC8B52E738"));
auto CheckOurselves = [&](const auto& entry)
{
REQUIRE(std::holds_alternative<Nz::VirtualDirectory::VirtualDirectoryEntry>(entry));
const auto& dirEntry = std::get<Nz::VirtualDirectory::VirtualDirectoryEntry>(entry);
return dirEntry.directory == resourceDir;
};
CHECK(resourceDir->GetEntry("..", CheckOurselves));
CHECK(resourceDir->GetEntry("../..", CheckOurselves));
CHECK(resourceDir->GetEntry("./..", CheckOurselves));
CHECK(resourceDir->GetEntry("./..", CheckOurselves));
CHECK(resourceDir->GetEntry("Audio/../..", CheckOurselves));
CHECK(resourceDir->GetEntry("Core/../Audio/../../..", CheckOurselves));
}
AND_THEN("Overriding the physical file with another one")
{
resourceDir->StoreFile("Logo.png", GetAssetDir() / "Audio/ambience.ogg");
CHECK(CheckFileHash(resourceDir, "Audio/ambience.ogg", "49C486F44E43F023D54C9F375D902C21375DDB2748D3FA1863C9581D30E17F94"));
CHECK(CheckFileHash(resourceDir, "Logo.png", "49C486F44E43F023D54C9F375D902C21375DDB2748D3FA1863C9581D30E17F94"));
CHECK(CheckFileContentHash(resourceDir, "Audio/ambience.ogg", "49C486F44E43F023D54C9F375D902C21375DDB2748D3FA1863C9581D30E17F94"));
CHECK(CheckFileContentHash(resourceDir, "Logo.png", "49C486F44E43F023D54C9F375D902C21375DDB2748D3FA1863C9581D30E17F94"));
}
WHEN("Accessing physical folder as a virtual folder")
{
CHECK(resourceDir->GetDirectoryEntry("Utility", [&](const Nz::VirtualDirectory::DirectoryEntry& directoryEntry)
{
bool found = false;
directoryEntry.directory->Foreach([&](std::string_view entryName, const Nz::VirtualDirectory::Entry& entry)
{
if (entryName == "GIF")
{
CHECK(std::holds_alternative<Nz::VirtualDirectory::PhysicalDirectoryEntry>(entry));
found = true;
}
});
return found;
}));
}
WHEN("Testing uproot escape")
{
std::shared_ptr<Nz::VirtualDirectory> engineDir = std::make_shared<Nz::VirtualDirectory>(GetAssetDir() / "Audio");
CHECK_FALSE(engineDir->IsUprootAllowed());
// We can't escape the virtual directory
CHECK_FALSE(engineDir->Exists("../Logo.png"));
CHECK_FALSE(engineDir->Exists("../../Logo.png"));
CHECK_FALSE(engineDir->Exists("../Tests/Audio/Audio/The_Brabanconne.ogg"));
CHECK(CheckFileHash(engineDir, "../../The_Brabanconne.ogg", "E07706E0BEEC7770CDE36008826743AF9EEE5C80CA0BD83C37771CBC8B52E738"));
CHECK(CheckFileHash(engineDir, ".././The_Brabanconne.ogg", "E07706E0BEEC7770CDE36008826743AF9EEE5C80CA0BD83C37771CBC8B52E738"));
engineDir->AllowUproot(true);
CHECK(engineDir->IsUprootAllowed());
// Now we're able to access the asset folder beneath
CHECK(CheckFileHash(engineDir, "../Logo.png", "5C4B9387327C039A6CE9ED51983D6C2ADA9F9DD01D024C2D5D588237ADFC7423"));
CHECK(CheckFileHash(engineDir, "../Audio/The_Brabanconne.ogg", "E07706E0BEEC7770CDE36008826743AF9EEE5C80CA0BD83C37771CBC8B52E738"));
}
}
}