winget-cli

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

ContextOrchestrator.cpp (6957B)


      1 // Copyright (c) Microsoft Corporation.
      2 // Licensed under the MIT License.
      3 #include "pch.h"
      4 #include "TestCommon.h"
      5 #include <ContextOrchestrator.h>
      6 #include <winget/ManifestYamlParser.h>
      7 
      8 using namespace TestCommon;
      9 using namespace AppInstaller::CLI;
     10 using namespace AppInstaller::CLI::Execution;
     11 using namespace AppInstaller::Manifest;
     12 
     13 static constexpr DWORD c_DefaultWaitInMs = 5000;
     14 
     15 struct TestCOMContext : public COMContext
     16 {
     17     TestCOMContext() = default;
     18 
     19     std::function<void()> DownloadCallback;
     20 
     21     void InvokeDownload()
     22     {
     23         if (DownloadCallback)
     24         {
     25             DownloadCallback();
     26         }
     27     }
     28 
     29     std::function<void()> OperationCallback;
     30 
     31     void InvokeOperation()
     32     {
     33         if (OperationCallback)
     34         {
     35             OperationCallback();
     36         }
     37     }
     38 };
     39 
     40 struct TestDownloadCommand : public Command
     41 {
     42     TestDownloadCommand() : Command("download", "tests") {}
     43 
     44     Resource::LocString ShortDescription() const override { return {}; }
     45     Resource::LocString LongDescription() const override { return {}; }
     46     void ValidateArguments(Execution::Args&) const override {}
     47 
     48     void Execute(Context& context) const override
     49     {
     50         static_cast<TestCOMContext*>(&context)->InvokeDownload();
     51     }
     52 };
     53 
     54 struct TestOperationCommand : public Command
     55 {
     56     TestOperationCommand() : Command("operation", "tests") {}
     57 
     58     Resource::LocString ShortDescription() const override { return {}; }
     59     Resource::LocString LongDescription() const override { return {}; }
     60     void ValidateArguments(Execution::Args&) const override {}
     61 
     62     void Execute(Context& context) const override
     63     {
     64         static_cast<TestCOMContext*>(&context)->InvokeOperation();
     65     }
     66 };
     67 
     68 struct TestQueueItem
     69 {
     70     TestCOMContext* Context = nullptr;
     71     std::shared_ptr<OrchestratorQueueItem> QueueItem;
     72 };
     73 
     74 TestQueueItem CreateTestItem(std::optional<std::string> packageName = std::nullopt)
     75 {
     76     TestQueueItem result;
     77 
     78     std::unique_ptr<TestCOMContext> context = std::make_unique<TestCOMContext>();
     79     // Forcibly initialize the thread globals objects
     80     context->GetThreadGlobals().SetForCurrentThread();
     81 
     82     TestDataFile testManifest("Manifest-Good.yaml");
     83     auto manifest = YamlParser::CreateFromPath(testManifest);
     84 
     85     if (packageName)
     86     {
     87         manifest.Id = packageName.value();
     88     }
     89 
     90     context->Add<Data::Manifest>(std::move(manifest));
     91 
     92     result.Context = context.get();
     93 
     94     // Marking it an uninstall removes the extra work adding the items to the installing index
     95     result.QueueItem = std::make_shared<OrchestratorQueueItem>(OrchestratorQueueItemId(AppInstaller::Utility::ConvertToUTF16(packageName.value_or("package")), L"source"), std::move(context), PackageOperationType::Uninstall);
     96 
     97     result.QueueItem->AddCommand(std::make_unique<TestDownloadCommand>());
     98     result.QueueItem->AddCommand(std::make_unique<TestOperationCommand>());
     99 
    100     return result;
    101 }
    102 
    103 // Runs an item through the orchestrator to ensure the basic functionality
    104 TEST_CASE("ContextOrchestrator_UnitTestExecution", "[context_orchestrator]")
    105 {
    106     ContextOrchestrator orchestrator;
    107 
    108     auto testItem = CreateTestItem();
    109 
    110     wil::slim_event_manual_reset operationEvent;
    111     testItem.Context->OperationCallback = [&]() { operationEvent.SetEvent(); };
    112 
    113     orchestrator.EnqueueAndRunItem(testItem.QueueItem);
    114 
    115     REQUIRE(operationEvent.wait(c_DefaultWaitInMs));
    116     REQUIRE(testItem.QueueItem->GetCompletedEvent().wait(c_DefaultWaitInMs));
    117     REQUIRE(orchestrator.WaitForRunningItems(c_DefaultWaitInMs));
    118 }
    119 
    120 TEST_CASE("ContextOrchestrator_Disabled_NewEnqueue", "[context_orchestrator]")
    121 {
    122     ContextOrchestrator orchestrator;
    123     auto testItem = CreateTestItem();
    124 
    125     auto reason = AppInstaller::CancelReason::AppShutdown;
    126     orchestrator.Disable(reason);
    127     REQUIRE_THROWS_HR(orchestrator.EnqueueAndRunItem(testItem.QueueItem), AppInstaller::ToHRESULT(reason));
    128 }
    129 
    130 TEST_CASE("ContextOrchestrator_Disabled_QueueTransition", "[context_orchestrator]")
    131 {
    132     ContextOrchestrator orchestrator;
    133 
    134     auto testItem = CreateTestItem();
    135 
    136     wil::slim_event_manual_reset downloadEvent;
    137     testItem.Context->DownloadCallback = [&]() { downloadEvent.wait(); };
    138 
    139     orchestrator.EnqueueAndRunItem(testItem.QueueItem);
    140 
    141     auto reason = AppInstaller::CancelReason::AppShutdown;
    142     orchestrator.Disable(reason);
    143 
    144     downloadEvent.SetEvent();
    145 
    146     REQUIRE(testItem.QueueItem->GetCompletedEvent().wait(c_DefaultWaitInMs));
    147     REQUIRE(testItem.Context->IsTerminated());
    148     // Context translates our shutdown HRs to E_ABORT
    149     REQUIRE(E_ABORT == testItem.Context->GetTerminationHR());
    150 }
    151 
    152 // While item in { Queued, Running } in both queues, cancel everything
    153 TEST_CASE("ContextOrchestrator_CancelAllItems", "[context_orchestrator]")
    154 {
    155     // Limit to one thread for downloads so we can get a queued item
    156     ContextOrchestrator orchestrator{ 1 };
    157 
    158     auto downloadQueued = CreateTestItem("downloadQueued");
    159     auto downloadRunning = CreateTestItem("downloadRunning");
    160     wil::slim_event_manual_reset downloadBegunEvent;
    161     wil::slim_event_manual_reset downloadWaitingEvent;
    162     downloadRunning.Context->DownloadCallback = [&]()
    163         {
    164             downloadBegunEvent.SetEvent();
    165             downloadWaitingEvent.wait();
    166         };
    167 
    168     auto operationQueued = CreateTestItem("operationQueued");
    169     auto operationRunning = CreateTestItem("operationRunning");
    170     wil::slim_event_manual_reset operationBegunEvent;
    171     wil::slim_event_manual_reset operationWaitingEvent;
    172     operationRunning.Context->OperationCallback = [&]()
    173         {
    174             operationBegunEvent.SetEvent();
    175             operationWaitingEvent.wait();
    176         };
    177 
    178     orchestrator.EnqueueAndRunItem(operationRunning.QueueItem);
    179     orchestrator.EnqueueAndRunItem(operationQueued.QueueItem);
    180     orchestrator.EnqueueAndRunItem(downloadRunning.QueueItem);
    181     orchestrator.EnqueueAndRunItem(downloadQueued.QueueItem);
    182 
    183     operationBegunEvent.wait(c_DefaultWaitInMs);
    184     downloadBegunEvent.wait(c_DefaultWaitInMs);
    185 
    186     INFO("Pre-shutdown state: \n" << orchestrator.GetStatusString());
    187 
    188     auto reason = AppInstaller::CancelReason::AppShutdown;
    189     orchestrator.Disable(reason);
    190     orchestrator.CancelQueuedItems(reason);
    191 
    192     operationWaitingEvent.SetEvent();
    193     downloadWaitingEvent.SetEvent();
    194 
    195     if (!orchestrator.WaitForRunningItems(c_DefaultWaitInMs))
    196     {
    197         INFO("Post-wait state: \n" << orchestrator.GetStatusString());
    198         FAIL("Timed out waiting for orchestrator to empty");
    199     }
    200 
    201     auto checkQueueItem = [](TestQueueItem& item)
    202         {
    203             REQUIRE(item.QueueItem->GetCompletedEvent().wait(0));
    204             REQUIRE(item.Context->IsTerminated());
    205             REQUIRE(E_ABORT == item.Context->GetTerminationHR());
    206         };
    207 
    208     checkQueueItem(downloadQueued);
    209     checkQueueItem(downloadRunning);
    210     checkQueueItem(operationQueued);
    211     checkQueueItem(operationRunning);
    212 }