winget-cli

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

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 }