FileCache.cpp (10617B)
1 // Copyright (c) Microsoft Corporation. 2 // Licensed under the MIT License. 3 #include "pch.h" 4 #include "TestCommon.h" 5 #include <AppInstallerStrings.h> 6 #include <winget/FileCache.h> 7 8 using namespace AppInstaller::Caching; 9 using namespace AppInstaller::Utility; 10 using namespace TestCommon; 11 12 struct TestFileCache 13 { 14 struct UpstreamFileInfo 15 { 16 TestDataFile OriginalFile; 17 std::filesystem::path Offset; 18 std::filesystem::path UpstreamPath; 19 std::vector<uint8_t> Contents; 20 SHA256::HashBuffer ContentHash; 21 }; 22 23 TestFileCache(std::string identifier = {}, size_t upstreamCount = 1) 24 { 25 if (identifier.empty()) 26 { 27 identifier = ConvertToUTF8(CreateNewGuidNameWString()); 28 } 29 30 std::vector<std::string> upstreamStrings; 31 32 for (size_t i = 0; i < upstreamCount; ++i) 33 { 34 UpstreamSources.emplace_back("TestFileCache"); 35 upstreamStrings.emplace_back(UpstreamSources.back().GetPath().u8string()); 36 } 37 38 CachePtr = std::make_unique<FileCache>(FileCache::Type::Tests, std::move(identifier), std::move(upstreamStrings)); 39 } 40 41 FileCache& Cache() { return *CachePtr; } 42 FileCache* operator->() { return CachePtr.get(); } 43 44 UpstreamFileInfo PrepareUpstreamFile(const std::filesystem::path& testDataFile, const std::filesystem::path& offset = {}, size_t index = 0) 45 { 46 UpstreamFileInfo result{ testDataFile }; 47 48 auto dataFilePath = result.OriginalFile.GetPath(); 49 50 result.Offset = offset.empty() ? dataFilePath.filename() : offset; 51 result.UpstreamPath = UpstreamSources[index].GetPath() / result.Offset; 52 53 std::filesystem::copy_file(dataFilePath, result.UpstreamPath); 54 55 std::ifstream fileStream{ dataFilePath, std::ios_base::in | std::ios_base::binary }; 56 result.Contents = ReadEntireStreamAsByteArray(fileStream); 57 58 result.ContentHash = SHA256::ComputeHash(result.Contents); 59 60 return result; 61 } 62 63 std::filesystem::path GetCacheFilePath(const UpstreamFileInfo& upstreamFileInfo) 64 { 65 std::filesystem::path result = CachePtr->GetDetails().GetCachePath() / upstreamFileInfo.Offset; 66 std::filesystem::create_directories(result.parent_path()); 67 return result; 68 } 69 70 std::unique_ptr<std::istream> GetFile(const UpstreamFileInfo& upstreamFileInfo) 71 { 72 return CachePtr->GetFile(upstreamFileInfo.Offset, upstreamFileInfo.ContentHash); 73 } 74 75 void RequireCachedFile(const UpstreamFileInfo& upstreamFileInfo) 76 { 77 std::filesystem::path cachedFilePath = GetCacheFilePath(upstreamFileInfo); 78 REQUIRE(std::filesystem::is_regular_file(cachedFilePath)); 79 REQUIRE(SHA256::AreEqual(upstreamFileInfo.ContentHash, SHA256::ComputeHashFromFile(cachedFilePath))); 80 } 81 82 std::unique_ptr<FileCache> CachePtr; 83 std::vector<TempDirectory> UpstreamSources; 84 }; 85 86 TEST_CASE("FileCache_TypeLocationsDiffer", "[file_cache]") 87 { 88 std::string identifier = "identifier"; 89 std::string identifier2 = "identifier2"; 90 91 REQUIRE(FileCache(FileCache::Type::IndexV1_Manifest, identifier, {}).GetDetails().GetCachePath() != FileCache(FileCache::Type::IndexV2_Manifest, identifier, {}).GetDetails().GetCachePath()); 92 REQUIRE(FileCache(FileCache::Type::IndexV1_Manifest, identifier, {}).GetDetails().GetCachePath() != FileCache(FileCache::Type::IndexV2_PackageVersionData, identifier, {}).GetDetails().GetCachePath()); 93 REQUIRE(FileCache(FileCache::Type::IndexV2_Manifest, identifier, {}).GetDetails().GetCachePath() != FileCache(FileCache::Type::IndexV2_PackageVersionData, identifier, {}).GetDetails().GetCachePath()); 94 95 REQUIRE(FileCache(FileCache::Type::IndexV1_Manifest, identifier, {}).GetDetails().GetCachePath() != FileCache(FileCache::Type::IndexV1_Manifest, identifier2, {}).GetDetails().GetCachePath()); 96 REQUIRE(FileCache(FileCache::Type::IndexV2_Manifest, identifier, {}).GetDetails().GetCachePath() != FileCache(FileCache::Type::IndexV2_Manifest, identifier2, {}).GetDetails().GetCachePath()); 97 REQUIRE(FileCache(FileCache::Type::IndexV2_PackageVersionData, identifier, {}).GetDetails().GetCachePath() != FileCache(FileCache::Type::IndexV2_PackageVersionData, identifier2, {}).GetDetails().GetCachePath()); 98 } 99 100 TEST_CASE("FileCache_TypeLocationsSame", "[file_cache]") 101 { 102 std::string identifier = "identifier"; 103 std::string source = "source"; 104 105 REQUIRE(FileCache(FileCache::Type::IndexV1_Manifest, identifier, {}).GetDetails().GetCachePath() == FileCache(FileCache::Type::IndexV1_Manifest, identifier, { source }).GetDetails().GetCachePath()); 106 REQUIRE(FileCache(FileCache::Type::IndexV2_Manifest, identifier, {}).GetDetails().GetCachePath() == FileCache(FileCache::Type::IndexV2_Manifest, identifier, { source }).GetDetails().GetCachePath()); 107 REQUIRE(FileCache(FileCache::Type::IndexV2_PackageVersionData, identifier, {}).GetDetails().GetCachePath() == FileCache(FileCache::Type::IndexV2_PackageVersionData, identifier, { source }).GetDetails().GetCachePath()); 108 } 109 110 TEST_CASE("FileCache_NoCachedFile", "[file_cache]") 111 { 112 TestFileCache testFileCache; 113 INFO("Cache location: " << testFileCache->GetDetails().GetCachePath().u8string()); 114 115 auto sourceFile = testFileCache.PrepareUpstreamFile("Manifest-Good-SystemReferenceComplex.yaml"); 116 117 auto cachedStream = testFileCache.GetFile(sourceFile); 118 119 REQUIRE(cachedStream); 120 REQUIRE(SHA256::AreEqual(sourceFile.ContentHash, SHA256::ComputeHash(ReadEntireStreamAsByteArray(*cachedStream)))); 121 122 testFileCache.RequireCachedFile(sourceFile); 123 } 124 125 TEST_CASE("FileCache_CachedFileIsDirectory", "[file_cache]") 126 { 127 TestFileCache testFileCache; 128 INFO("Cache location: " << testFileCache->GetDetails().GetCachePath().u8string()); 129 130 auto sourceFile = testFileCache.PrepareUpstreamFile("Manifest-Good.yaml"); 131 std::filesystem::create_directories(testFileCache.GetCacheFilePath(sourceFile)); 132 133 auto cachedStream = testFileCache.GetFile(sourceFile); 134 135 REQUIRE(cachedStream); 136 REQUIRE(SHA256::AreEqual(sourceFile.ContentHash, SHA256::ComputeHash(ReadEntireStreamAsByteArray(*cachedStream)))); 137 138 testFileCache.RequireCachedFile(sourceFile); 139 } 140 141 TEST_CASE("FileCache_CachedFileGoodHash", "[file_cache]") 142 { 143 TestFileCache testFileCache; 144 INFO("Cache location: " << testFileCache->GetDetails().GetCachePath().u8string()); 145 146 auto sourceFile = testFileCache.PrepareUpstreamFile("InstallFlowTest_MSStore.yaml"); 147 std::filesystem::copy_file(sourceFile.OriginalFile, testFileCache.GetCacheFilePath(sourceFile)); 148 149 auto cachedStream = testFileCache.GetFile(sourceFile); 150 151 REQUIRE(cachedStream); 152 REQUIRE(SHA256::AreEqual(sourceFile.ContentHash, SHA256::ComputeHash(ReadEntireStreamAsByteArray(*cachedStream)))); 153 154 testFileCache.RequireCachedFile(sourceFile); 155 } 156 157 TEST_CASE("FileCache_CachedFileBadHash", "[file_cache]") 158 { 159 TestFileCache testFileCache; 160 INFO("Cache location: " << testFileCache->GetDetails().GetCachePath().u8string()); 161 162 auto sourceFile = testFileCache.PrepareUpstreamFile("ManifestV1-MultiFile-Version.yaml"); 163 std::filesystem::copy_file(TestDataFile("Manifest-Bad-ProductCodeOnMSIX.yaml"), testFileCache.GetCacheFilePath(sourceFile)); 164 165 auto cachedStream = testFileCache.GetFile(sourceFile); 166 167 REQUIRE(cachedStream); 168 REQUIRE(SHA256::AreEqual(sourceFile.ContentHash, SHA256::ComputeHash(ReadEntireStreamAsByteArray(*cachedStream)))); 169 170 testFileCache.RequireCachedFile(sourceFile); 171 } 172 173 TEST_CASE("FileCache_CachedFileLockedExclusive", "[file_cache]") 174 { 175 TestFileCache testFileCache; 176 INFO("Cache location: " << testFileCache->GetDetails().GetCachePath().u8string()); 177 178 auto sourceFile = testFileCache.PrepareUpstreamFile("ManifestV1-MultiFile-Installer.yaml"); 179 TestDataFile wrongFileOriginal = TestDataFile("Manifest-Bad-InvalidLocale.yaml"); 180 std::filesystem::path wrongFilePath = testFileCache.GetCacheFilePath(sourceFile); 181 std::filesystem::copy_file(wrongFileOriginal, wrongFilePath); 182 wil::unique_handle exclusiveFileHandle{ CreateFileW(wrongFilePath.c_str(), GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL) }; 183 184 auto cachedStream = testFileCache.GetFile(sourceFile); 185 186 REQUIRE(cachedStream); 187 REQUIRE(SHA256::AreEqual(sourceFile.ContentHash, SHA256::ComputeHash(ReadEntireStreamAsByteArray(*cachedStream)))); 188 } 189 190 TEST_CASE("FileCache_FirstUpstreamDoesNotHaveFile", "[file_cache]") 191 { 192 TestFileCache testFileCache({}, 2); 193 INFO("Cache location: " << testFileCache->GetDetails().GetCachePath().u8string()); 194 195 auto sourceFile = testFileCache.PrepareUpstreamFile("Manifest-Good-MultiLocale.yaml", {}, 1); 196 197 auto cachedStream = testFileCache.GetFile(sourceFile); 198 199 REQUIRE(cachedStream); 200 REQUIRE(SHA256::AreEqual(sourceFile.ContentHash, SHA256::ComputeHash(ReadEntireStreamAsByteArray(*cachedStream)))); 201 202 testFileCache.RequireCachedFile(sourceFile); 203 } 204 205 TEST_CASE("FileCache_FirstUpstreamHasBadHash", "[file_cache]") 206 { 207 TestFileCache testFileCache({}, 2); 208 INFO("Cache location: " << testFileCache->GetDetails().GetCachePath().u8string()); 209 210 auto badFile = testFileCache.PrepareUpstreamFile("Manifest-Bad-VersionMissing.yaml", {}, 0); 211 auto sourceFile = testFileCache.PrepareUpstreamFile("Manifest-Good-MultiLocale.yaml", {}, 1); 212 213 auto cachedStream = testFileCache.GetFile(sourceFile); 214 215 REQUIRE(cachedStream); 216 REQUIRE(SHA256::AreEqual(sourceFile.ContentHash, SHA256::ComputeHash(ReadEntireStreamAsByteArray(*cachedStream)))); 217 218 testFileCache.RequireCachedFile(sourceFile); 219 } 220 221 TEST_CASE("FileCache_NoUpstreamSources", "[file_cache]") 222 { 223 TestFileCache testFileCache("", 0); 224 INFO("Cache location: " << testFileCache->GetDetails().GetCachePath().u8string()); 225 226 REQUIRE_THROWS_HR(testFileCache->GetFile("any_file", SHA256::ComputeHash("garbage")), E_NOT_SET); 227 } 228 229 TEST_CASE("FileCache_PathTooLong", "[file_cache]") 230 { 231 TestFileCache testFileCache(std::string(260, 'a')); 232 INFO("Cache location: " << testFileCache->GetDetails().GetCachePath().u8string()); 233 234 auto sourceFile = testFileCache.PrepareUpstreamFile("Manifest-Good-SystemReferenceComplex.yaml"); 235 236 auto cachedStream = testFileCache.GetFile(sourceFile); 237 238 REQUIRE(cachedStream); 239 REQUIRE(SHA256::AreEqual(sourceFile.ContentHash, SHA256::ComputeHash(ReadEntireStreamAsByteArray(*cachedStream)))); 240 }