#include #include #include #include #include #include #include #include std::filesystem::path GetAssetDir(); TEST_CASE("VirtualDirectory", "[Core][VirtualDirectory]") { SECTION("Creating a virtual directory") { std::shared_ptr virtualDir = std::make_shared(); 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); }, false, 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()); CHECK(virtualDir->GetEntry("Foo", [](const Nz::VirtualDirectory::Entry& entry) { return std::holds_alternative(entry); })); CHECK(virtualDir->GetEntry("Foo/Bar", [](const Nz::VirtualDirectory::Entry& entry) { return std::holds_alternative(entry); })); CHECK_FALSE(virtualDir->GetEntry("Foo/Bar/File.bin", [](const Nz::VirtualDirectory::Entry& /*entry*/) { return true; })); } std::mt19937 randGen(std::random_device{}()); auto GenerateRandomData = [&] { Nz::ByteArray randomData; for (std::size_t i = 0; i < 256; ++i) { unsigned int data = randGen(); randomData.PushBack(Nz::SafeCast((data & 0x000000FF) >> 0)); randomData.PushBack(Nz::SafeCast((data & 0x0000FF00) >> 8)); randomData.PushBack(Nz::SafeCast((data & 0x00FF0000) >> 16)); randomData.PushBack(Nz::SafeCast((data & 0xFF000000) >> 24)); } return randomData; }; auto CheckFile = [&](std::string_view path, const Nz::ByteArray& expectedData) { return virtualDir->GetFileContent(path, [&](const void* data, std::size_t length) { return length == expectedData.size() && std::memcmp(data, &expectedData[0], length) == 0; }); }; auto CheckFileContent = [&](std::string_view path, const Nz::ByteArray& 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[0], 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; Nz::ByteArray data; }; std::vector files; std::unordered_map 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 resourceDir = std::make_shared(std::make_shared(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(entry) || std::holds_alternative(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(entry)); const auto& physFileEntry = std::get(entry); Nz::SHA256Hasher hash; WHEN("We compute " << hash.GetHashName() << " of " << physFileEntry.stream->GetPath() << " file") { CHECK(Nz::ToUpper(Nz::ComputeHash(hash, *physFileEntry.stream).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::SHA256Hasher hash; WHEN("We compute " << hash.GetHashName() << " of " << filepath << " file") { hash.Begin(); hash.Append(static_cast(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(entry)); const auto& dirEntry = std::get(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("Utility/../Audio/../../..", CheckOurselves)); } AND_THEN("Overriding the physical file with another one") { resourceDir->StoreFile("Logo.png", std::make_shared(GetAssetDir() / "Audio/ambience.ogg", Nz::OpenMode::Read)); 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(entry)); found = true; } }); return found; })); } WHEN("Testing uproot escape") { std::shared_ptr engineDir = std::make_shared(std::make_shared(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")); } } }