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 }