mirror of
https://github.com/aphrody-labs/WSL.git
synced 2026-10-14 05:39:06 +00:00
wsltests is a single large target whose compile time dominates local test iteration. Enabling CMake unity builds batches test sources into shared translation units, cutting wall-clock build time significantly. Batch size is controlled by the new WSL_TEST_UNITY_BATCH_SIZE cache variable (default 2). Setting it to 0 disables unity builds and compiles every file separately, which is useful for isolating a build break to a single source file. The value is validated at configure time and documented in UserConfig.cmake.sample. Concatenating sources into one translation unit surfaced latent collisions that file scope had previously hidden: - g_pipelineBuildId was defined as a separate file-static in both Common.cpp and InstallerTests.cpp. Common.cpp now owns the definition and InstallerTests.cpp declares it extern. - ProcessOutput was declared at namespace scope in both WslcSdkTests.cpp and WslcSdkWinRTTests.cpp. The latter is renamed to CapturedProcessOutput. - Broad `using namespace wsl::windows::common;` directives in WindowsUpdateTests.cpp, WSLCE2EHelpers.cpp, WSLCE2ETlsRegistryTests.cpp and WSLCExecutor.cpp leaked into neighboring sources and created ambiguous lookups. They are replaced with targeted using-declarations and namespace aliases. Unity builds also change test execution order, which broke assertions that implicitly depended on running before anything else had created a session. WSLCTests::ListSessions and WSLCTests::SessionManagement now share a ListTestSessionNames() helper that filters out the persistent sessions the wslc CLI creates for itself, so the assertions no longer depend on what else has run on the machine. SessionManagement is the only test that creates persistent sessions, which outlive the COM reference that created them. It now records the ones it creates and terminates them from a scope_exit handler, so a failure partway through cannot leak a session into subsequent runs. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1051 lines
32 KiB
C++
1051 lines
32 KiB
C++
/*++
|
||
|
||
Copyright (c) Microsoft. All rights reserved.
|
||
|
||
Module Name:
|
||
|
||
WindowsUpdateTests.cpp
|
||
|
||
Abstract:
|
||
|
||
This file contains unit tests for WindowsUpdateIntegration.cpp.
|
||
These tests use mock COM objects injected via the WindowsUpdateClassFactory
|
||
abstraction so no real Windows Update service calls are made.
|
||
|
||
--*/
|
||
|
||
#include "precomp.h"
|
||
#include "Common.h"
|
||
#include "WindowsUpdateIntegration.h"
|
||
|
||
using wsl::windows::common::WindowsUpdateClassFactory;
|
||
using wsl::windows::common::WindowsUpdateContext;
|
||
namespace WRL = Microsoft::WRL;
|
||
|
||
namespace {
|
||
|
||
// Stubs the 4 IDispatch pure virtual methods. All WUA interfaces derive from IDispatch.
|
||
#define STUB_IDISPATCH() \
|
||
STDMETHOD(GetTypeInfoCount)(UINT*) override \
|
||
{ \
|
||
return E_NOTIMPL; \
|
||
} \
|
||
STDMETHOD(GetTypeInfo)(UINT, LCID, ITypeInfo**) override \
|
||
{ \
|
||
return E_NOTIMPL; \
|
||
} \
|
||
STDMETHOD(GetIDsOfNames)(REFIID, LPOLESTR*, UINT, LCID, DISPID*) override \
|
||
{ \
|
||
return E_NOTIMPL; \
|
||
} \
|
||
STDMETHOD(Invoke)(DISPID, REFIID, LCID, WORD, DISPPARAMS*, VARIANT*, EXCEPINFO*, UINT*) override \
|
||
{ \
|
||
return E_NOTIMPL; \
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// MockUpdateCollection — implements IUpdateCollection
|
||
// ---------------------------------------------------------------------------
|
||
struct MockUpdateCollection : public WRL::RuntimeClass<WRL::RuntimeClassFlags<WRL::ClassicCom>, IUpdateCollection>
|
||
{
|
||
STUB_IDISPATCH()
|
||
|
||
std::vector<wil::com_ptr<IUpdate>> items;
|
||
LONG addCallCount = 0;
|
||
|
||
STDMETHOD(get_Count)(LONG* retval) override
|
||
{
|
||
*retval = static_cast<LONG>(items.size());
|
||
return S_OK;
|
||
}
|
||
|
||
STDMETHOD(get_Item)(LONG index, IUpdate** retval) override
|
||
{
|
||
if (index < 0 || static_cast<size_t>(index) >= items.size())
|
||
{
|
||
return E_INVALIDARG;
|
||
}
|
||
*retval = items[index].get();
|
||
(*retval)->AddRef();
|
||
return S_OK;
|
||
}
|
||
|
||
STDMETHOD(Add)(IUpdate* value, LONG*) override
|
||
{
|
||
wil::com_ptr<IUpdate> u = value;
|
||
items.push_back(std::move(u));
|
||
++addCallCount;
|
||
return S_OK;
|
||
}
|
||
|
||
STDMETHOD(Clear)() override
|
||
{
|
||
items.clear();
|
||
return S_OK;
|
||
}
|
||
|
||
STDMETHOD(put_Item)(LONG, IUpdate*) override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
STDMETHOD(get__NewEnum)(IUnknown**) override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
STDMETHOD(get_ReadOnly)(VARIANT_BOOL*) override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
STDMETHOD(Copy)(IUpdateCollection**) override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
STDMETHOD(Insert)(LONG, IUpdate*) override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
STDMETHOD(RemoveAt)(LONG) override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
};
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// MockUpdate — implements IUpdate
|
||
// ---------------------------------------------------------------------------
|
||
struct MockUpdate : public WRL::RuntimeClass<WRL::RuntimeClassFlags<WRL::ClassicCom>, IUpdate>
|
||
{
|
||
STUB_IDISPATCH()
|
||
|
||
VARIANT_BOOL isDownloaded = VARIANT_FALSE;
|
||
|
||
STDMETHOD(get_IsDownloaded)(VARIANT_BOOL* retval) override
|
||
{
|
||
*retval = isDownloaded;
|
||
return S_OK;
|
||
}
|
||
|
||
STDMETHOD(get_Title)(BSTR*) override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
STDMETHOD(get_AutoSelectOnWebSites)(VARIANT_BOOL*) override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
STDMETHOD(get_BundledUpdates)(IUpdateCollection**) override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
STDMETHOD(get_CanRequireSource)(VARIANT_BOOL*) override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
STDMETHOD(get_Categories)(ICategoryCollection**) override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
STDMETHOD(get_Deadline)(VARIANT*) override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
STDMETHOD(get_DeltaCompressedContentAvailable)(VARIANT_BOOL*) override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
STDMETHOD(get_DeltaCompressedContentPreferred)(VARIANT_BOOL*) override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
STDMETHOD(get_Description)(BSTR*) override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
STDMETHOD(get_EulaAccepted)(VARIANT_BOOL*) override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
STDMETHOD(get_EulaText)(BSTR*) override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
STDMETHOD(get_HandlerID)(BSTR*) override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
STDMETHOD(get_Identity)(IUpdateIdentity**) override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
STDMETHOD(get_Image)(IImageInformation**) override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
STDMETHOD(get_InstallationBehavior)(IInstallationBehavior**) override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
STDMETHOD(get_IsBeta)(VARIANT_BOOL*) override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
STDMETHOD(get_IsHidden)(VARIANT_BOOL*) override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
STDMETHOD(put_IsHidden)(VARIANT_BOOL) override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
STDMETHOD(get_IsInstalled)(VARIANT_BOOL*) override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
STDMETHOD(get_IsMandatory)(VARIANT_BOOL*) override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
STDMETHOD(get_IsUninstallable)(VARIANT_BOOL*) override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
STDMETHOD(get_Languages)(IStringCollection**) override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
STDMETHOD(get_LastDeploymentChangeTime)(DATE*) override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
STDMETHOD(get_MaxDownloadSize)(DECIMAL*) override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
STDMETHOD(get_MinDownloadSize)(DECIMAL*) override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
STDMETHOD(get_MoreInfoUrls)(IStringCollection**) override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
STDMETHOD(get_MsrcSeverity)(BSTR*) override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
STDMETHOD(get_RecommendedCpuSpeed)(LONG*) override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
STDMETHOD(get_RecommendedHardDiskSpace)(LONG*) override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
STDMETHOD(get_RecommendedMemory)(LONG*) override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
STDMETHOD(get_ReleaseNotes)(BSTR*) override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
STDMETHOD(get_SecurityBulletinIDs)(IStringCollection**) override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
STDMETHOD(get_SupersededUpdateIDs)(IStringCollection**) override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
STDMETHOD(get_SupportUrl)(BSTR*) override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
STDMETHOD(get_Type)(UpdateType*) override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
STDMETHOD(get_UninstallationNotes)(BSTR*) override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
STDMETHOD(get_UninstallationBehavior)(IInstallationBehavior**) override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
STDMETHOD(get_UninstallationSteps)(IStringCollection**) override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
STDMETHOD(get_KBArticleIDs)(IStringCollection**) override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
STDMETHOD(AcceptEula)() override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
STDMETHOD(get_DeploymentAction)(DeploymentAction*) override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
STDMETHOD(CopyFromCache)(BSTR, VARIANT_BOOL) override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
STDMETHOD(get_DownloadPriority)(DownloadPriority*) override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
STDMETHOD(get_DownloadContents)(IUpdateDownloadContentCollection**) override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
};
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// MockSearchResult — implements ISearchResult
|
||
// ---------------------------------------------------------------------------
|
||
struct MockSearchResult : public WRL::RuntimeClass<WRL::RuntimeClassFlags<WRL::ClassicCom>, ISearchResult>
|
||
{
|
||
STUB_IDISPATCH()
|
||
|
||
OperationResultCode resultCode = OperationResultCode::orcSucceeded;
|
||
wil::com_ptr_nothrow<MockUpdateCollection> updates = wil::MakeOrThrow<MockUpdateCollection>();
|
||
|
||
STDMETHOD(get_ResultCode)(OperationResultCode* retval) override
|
||
{
|
||
*retval = resultCode;
|
||
return S_OK;
|
||
}
|
||
|
||
STDMETHOD(get_Updates)(IUpdateCollection** retval) override
|
||
{
|
||
return updates.query_to(retval);
|
||
}
|
||
|
||
STDMETHOD(get_RootCategories)(ICategoryCollection**) override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
STDMETHOD(get_Warnings)(IUpdateExceptionCollection**) override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
};
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// MockUpdateSearcher — implements IUpdateSearcher
|
||
// ---------------------------------------------------------------------------
|
||
struct MockUpdateSearcher : public WRL::RuntimeClass<WRL::RuntimeClassFlags<WRL::ClassicCom>, IUpdateSearcher>
|
||
{
|
||
STUB_IDISPATCH()
|
||
|
||
wil::com_ptr_nothrow<MockSearchResult> searchResult = wil::MakeOrThrow<MockSearchResult>();
|
||
|
||
STDMETHOD(Search)(BSTR, ISearchResult** retval) override
|
||
{
|
||
return searchResult.query_to(retval);
|
||
}
|
||
|
||
STDMETHOD(get_CanAutomaticallyUpgradeService)(VARIANT_BOOL*) override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
STDMETHOD(put_CanAutomaticallyUpgradeService)(VARIANT_BOOL) override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
STDMETHOD(get_ClientApplicationID)(BSTR*) override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
STDMETHOD(put_ClientApplicationID)(BSTR) override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
STDMETHOD(get_IncludePotentiallySupersededUpdates)(VARIANT_BOOL*) override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
STDMETHOD(put_IncludePotentiallySupersededUpdates)(VARIANT_BOOL) override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
STDMETHOD(get_ServerSelection)(ServerSelection*) override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
STDMETHOD(put_ServerSelection)(ServerSelection) override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
STDMETHOD(BeginSearch)(BSTR, IUnknown*, VARIANT, ISearchJob**) override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
STDMETHOD(EndSearch)(ISearchJob*, ISearchResult**) override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
STDMETHOD(EscapeString)(BSTR, BSTR*) override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
STDMETHOD(QueryHistory)(LONG, LONG, IUpdateHistoryEntryCollection**) override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
STDMETHOD(get_Online)(VARIANT_BOOL*) override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
STDMETHOD(put_Online)(VARIANT_BOOL) override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
STDMETHOD(GetTotalHistoryCount)(LONG*) override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
STDMETHOD(get_ServiceID)(BSTR*) override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
STDMETHOD(put_ServiceID)(BSTR) override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
};
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// MockDownloadJob — implements IDownloadJob
|
||
// ---------------------------------------------------------------------------
|
||
struct MockDownloadJob : public WRL::RuntimeClass<WRL::RuntimeClassFlags<WRL::ClassicCom>, IDownloadJob>
|
||
{
|
||
STUB_IDISPATCH()
|
||
|
||
STDMETHOD(CleanUp)() override
|
||
{
|
||
return S_OK;
|
||
}
|
||
STDMETHOD(get_AsyncState)(VARIANT*) override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
STDMETHOD(get_IsCompleted)(VARIANT_BOOL*) override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
STDMETHOD(get_Updates)(IUpdateCollection**) override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
STDMETHOD(GetProgress)(IDownloadProgress**) override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
STDMETHOD(RequestAbort)() override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
};
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// MockDownloadResult — implements IDownloadResult
|
||
// ---------------------------------------------------------------------------
|
||
struct MockDownloadResult : public WRL::RuntimeClass<WRL::RuntimeClassFlags<WRL::ClassicCom>, IDownloadResult>
|
||
{
|
||
STUB_IDISPATCH()
|
||
|
||
HRESULT downloadHResult = S_OK;
|
||
|
||
STDMETHOD(get_HResult)(HRESULT* retval) override
|
||
{
|
||
*retval = downloadHResult;
|
||
return S_OK;
|
||
}
|
||
|
||
STDMETHOD(get_ResultCode)(OperationResultCode*) override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
STDMETHOD(GetUpdateResult)(LONG, IUpdateDownloadResult**) override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
};
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// MockUpdateDownloader — implements IUpdateDownloader
|
||
// ---------------------------------------------------------------------------
|
||
struct MockUpdateDownloader : public WRL::RuntimeClass<WRL::RuntimeClassFlags<WRL::ClassicCom>, IUpdateDownloader>
|
||
{
|
||
STUB_IDISPATCH()
|
||
|
||
wil::com_ptr_nothrow<MockDownloadResult> downloadResult = wil::MakeOrThrow<MockDownloadResult>();
|
||
wil::com_ptr<IUpdateCollection> capturedCollection;
|
||
bool beginDownloadCalled = false;
|
||
|
||
STDMETHOD(put_Updates)(IUpdateCollection* value) override
|
||
{
|
||
capturedCollection = value;
|
||
return S_OK;
|
||
}
|
||
|
||
STDMETHOD(BeginDownload)(IUnknown*, IUnknown* completedCallback, VARIANT, IDownloadJob** retval) override
|
||
{
|
||
beginDownloadCalled = true;
|
||
*retval = wil::MakeOrThrow<MockDownloadJob>().Detach();
|
||
if (completedCallback)
|
||
{
|
||
wil::com_ptr<IDownloadCompletedCallback> cb;
|
||
if (SUCCEEDED(completedCallback->QueryInterface(IID_PPV_ARGS(&cb))))
|
||
{
|
||
cb->Invoke(*retval, nullptr);
|
||
}
|
||
}
|
||
return S_OK;
|
||
}
|
||
|
||
STDMETHOD(EndDownload)(IDownloadJob*, IDownloadResult** retval) override
|
||
{
|
||
return downloadResult.query_to(retval);
|
||
}
|
||
|
||
STDMETHOD(get_ClientApplicationID)(BSTR*) override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
STDMETHOD(put_ClientApplicationID)(BSTR) override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
STDMETHOD(get_IsForced)(VARIANT_BOOL*) override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
STDMETHOD(put_IsForced)(VARIANT_BOOL) override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
STDMETHOD(get_Priority)(DownloadPriority*) override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
STDMETHOD(put_Priority)(DownloadPriority) override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
STDMETHOD(get_Updates)(IUpdateCollection**) override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
STDMETHOD(Download)(IDownloadResult**) override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
};
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// MockInstallationJob — implements IInstallationJob
|
||
// ---------------------------------------------------------------------------
|
||
struct MockInstallationJob : public WRL::RuntimeClass<WRL::RuntimeClassFlags<WRL::ClassicCom>, IInstallationJob>
|
||
{
|
||
STUB_IDISPATCH()
|
||
|
||
STDMETHOD(CleanUp)() override
|
||
{
|
||
return S_OK;
|
||
}
|
||
STDMETHOD(get_AsyncState)(VARIANT*) override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
STDMETHOD(get_IsCompleted)(VARIANT_BOOL*) override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
STDMETHOD(get_Updates)(IUpdateCollection**) override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
STDMETHOD(GetProgress)(IInstallationProgress**) override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
STDMETHOD(RequestAbort)() override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
};
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// MockInstallationResult — implements IInstallationResult
|
||
// ---------------------------------------------------------------------------
|
||
struct MockInstallationResult : public WRL::RuntimeClass<WRL::RuntimeClassFlags<WRL::ClassicCom>, IInstallationResult>
|
||
{
|
||
STUB_IDISPATCH()
|
||
|
||
HRESULT installHResult = S_OK;
|
||
|
||
STDMETHOD(get_HResult)(HRESULT* retval) override
|
||
{
|
||
*retval = installHResult;
|
||
return S_OK;
|
||
}
|
||
|
||
STDMETHOD(get_RebootRequired)(VARIANT_BOOL*) override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
STDMETHOD(get_ResultCode)(OperationResultCode*) override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
STDMETHOD(GetUpdateResult)(LONG, IUpdateInstallationResult**) override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
};
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// MockUpdateInstaller — implements IUpdateInstaller
|
||
// ---------------------------------------------------------------------------
|
||
struct MockUpdateInstaller : public WRL::RuntimeClass<WRL::RuntimeClassFlags<WRL::ClassicCom>, IUpdateInstaller>
|
||
{
|
||
STUB_IDISPATCH()
|
||
|
||
wil::com_ptr_nothrow<MockInstallationResult> installResult = wil::MakeOrThrow<MockInstallationResult>();
|
||
wil::com_ptr<IUpdateCollection> capturedCollection;
|
||
bool beginInstallCalled = false;
|
||
|
||
STDMETHOD(put_Updates)(IUpdateCollection* value) override
|
||
{
|
||
capturedCollection = value;
|
||
return S_OK;
|
||
}
|
||
|
||
STDMETHOD(BeginInstall)(IUnknown*, IUnknown* completedCallback, VARIANT, IInstallationJob** retval) override
|
||
{
|
||
beginInstallCalled = true;
|
||
*retval = wil::MakeOrThrow<MockInstallationJob>().Detach();
|
||
if (completedCallback)
|
||
{
|
||
wil::com_ptr<IInstallationCompletedCallback> cb;
|
||
if (SUCCEEDED(completedCallback->QueryInterface(IID_PPV_ARGS(&cb))))
|
||
{
|
||
cb->Invoke(*retval, nullptr);
|
||
}
|
||
}
|
||
return S_OK;
|
||
}
|
||
|
||
STDMETHOD(EndInstall)(IInstallationJob*, IInstallationResult** retval) override
|
||
{
|
||
return installResult.query_to(retval);
|
||
}
|
||
|
||
STDMETHOD(get_ClientApplicationID)(BSTR*) override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
STDMETHOD(put_ClientApplicationID)(BSTR) override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
STDMETHOD(get_IsForced)(VARIANT_BOOL*) override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
STDMETHOD(put_IsForced)(VARIANT_BOOL) override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
STDMETHOD(get_ParentHwnd)(HWND*) override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
STDMETHOD(put_ParentHwnd)(HWND) override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
STDMETHOD(put_ParentWindow)(IUnknown*) override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
STDMETHOD(get_ParentWindow)(IUnknown**) override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
STDMETHOD(get_Updates)(IUpdateCollection**) override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
STDMETHOD(BeginUninstall)(IUnknown*, IUnknown*, VARIANT, IInstallationJob**) override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
STDMETHOD(EndUninstall)(IInstallationJob*, IInstallationResult**) override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
STDMETHOD(Install)(IInstallationResult**) override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
STDMETHOD(RunWizard)(BSTR, IInstallationResult**) override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
STDMETHOD(get_IsBusy)(VARIANT_BOOL*) override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
STDMETHOD(Uninstall)(IInstallationResult**) override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
STDMETHOD(get_AllowSourcePrompts)(VARIANT_BOOL*) override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
STDMETHOD(put_AllowSourcePrompts)(VARIANT_BOOL) override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
STDMETHOD(get_RebootRequiredBeforeInstallation)(VARIANT_BOOL*) override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
};
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// MockUpdateSession — implements IUpdateSession
|
||
// ---------------------------------------------------------------------------
|
||
struct MockUpdateSession : public WRL::RuntimeClass<WRL::RuntimeClassFlags<WRL::ClassicCom>, IUpdateSession>
|
||
{
|
||
STUB_IDISPATCH()
|
||
|
||
wil::com_ptr_nothrow<MockUpdateSearcher> searcher = wil::MakeOrThrow<MockUpdateSearcher>();
|
||
wil::com_ptr_nothrow<MockUpdateDownloader> downloader = wil::MakeOrThrow<MockUpdateDownloader>();
|
||
wil::com_ptr_nothrow<MockUpdateInstaller> installer = wil::MakeOrThrow<MockUpdateInstaller>();
|
||
|
||
STDMETHOD(put_ClientApplicationID)(BSTR) override
|
||
{
|
||
return S_OK;
|
||
}
|
||
|
||
STDMETHOD(CreateUpdateSearcher)(IUpdateSearcher** retval) override
|
||
{
|
||
return searcher.query_to(retval);
|
||
}
|
||
|
||
STDMETHOD(CreateUpdateDownloader)(IUpdateDownloader** retval) override
|
||
{
|
||
return downloader.query_to(retval);
|
||
}
|
||
|
||
STDMETHOD(CreateUpdateInstaller)(IUpdateInstaller** retval) override
|
||
{
|
||
return installer.query_to(retval);
|
||
}
|
||
|
||
STDMETHOD(get_ClientApplicationID)(BSTR*) override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
STDMETHOD(get_ReadOnly)(VARIANT_BOOL*) override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
STDMETHOD(get_WebProxy)(IWebProxy**) override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
STDMETHOD(put_WebProxy)(IWebProxy*) override
|
||
{
|
||
return E_NOTIMPL;
|
||
}
|
||
};
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// MockWindowsUpdateClassFactory
|
||
// ---------------------------------------------------------------------------
|
||
struct MockWindowsUpdateClassFactory : public WindowsUpdateClassFactory
|
||
{
|
||
wil::com_ptr<MockUpdateSession> session = wil::MakeOrThrow<MockUpdateSession>();
|
||
// Tracks the most-recently created collection (used as toDownload in DownloadUpdates).
|
||
mutable wil::com_ptr<MockUpdateCollection> lastCreatedCollection;
|
||
|
||
wil::com_ptr<IUpdateSession> CreateUpdateSession() const override
|
||
{
|
||
return session.query<IUpdateSession>();
|
||
}
|
||
|
||
wil::com_ptr<IUpdateCollection> CreateUpdateCollection() const override
|
||
{
|
||
auto col = wil::MakeOrThrow<MockUpdateCollection>();
|
||
lastCreatedCollection = col;
|
||
wil::com_ptr<IUpdateCollection> result;
|
||
col.CopyTo(IID_IUpdateCollection, result.put_void());
|
||
return result;
|
||
}
|
||
};
|
||
|
||
// Captures the HRESULT thrown by a wil::ResultException, or S_OK if no exception.
|
||
static HRESULT CaptureHResult(const std::function<void()>& fn)
|
||
{
|
||
try
|
||
{
|
||
fn();
|
||
return S_OK;
|
||
}
|
||
catch (const wil::ResultException& e)
|
||
{
|
||
return e.GetErrorCode();
|
||
}
|
||
}
|
||
|
||
// Adds a MockUpdate with the given isDownloaded state to the search result collection.
|
||
static wil::com_ptr<MockUpdate> AddMockUpdate(MockUpdateCollection* col, VARIANT_BOOL isDownloaded)
|
||
{
|
||
auto u = wil::MakeOrThrow<MockUpdate>();
|
||
u->isDownloaded = isDownloaded;
|
||
wil::com_ptr<IUpdate> update;
|
||
u.CopyTo(IID_IUpdate, update.put_void());
|
||
col->items.push_back(std::move(update));
|
||
return u;
|
||
}
|
||
|
||
} // namespace
|
||
|
||
class WindowsUpdateTests
|
||
{
|
||
WSL_TEST_CLASS(WindowsUpdateTests)
|
||
|
||
// -----------------------------------------------------------------------
|
||
// SearchForUpdates tests
|
||
// -----------------------------------------------------------------------
|
||
|
||
TEST_METHOD(SearchForUpdates_NoUpdates)
|
||
{
|
||
auto factory = std::make_unique<MockWindowsUpdateClassFactory>();
|
||
WindowsUpdateContext ctx(std::move(factory));
|
||
|
||
VERIFY_ARE_EQUAL(0u, ctx.SearchForUpdates());
|
||
VERIFY_ARE_EQUAL(0u, ctx.GetUpdateCount());
|
||
}
|
||
|
||
TEST_METHOD(SearchForUpdates_UpdatesFound)
|
||
{
|
||
auto factory = std::make_unique<MockWindowsUpdateClassFactory>();
|
||
auto* fp = factory.get();
|
||
auto* col = fp->session->searcher->searchResult->updates.get();
|
||
AddMockUpdate(col, VARIANT_FALSE);
|
||
AddMockUpdate(col, VARIANT_FALSE);
|
||
AddMockUpdate(col, VARIANT_FALSE);
|
||
|
||
WindowsUpdateContext ctx(std::move(factory));
|
||
|
||
VERIFY_ARE_EQUAL(3u, ctx.SearchForUpdates());
|
||
VERIFY_ARE_EQUAL(3u, ctx.GetUpdateCount());
|
||
}
|
||
|
||
TEST_METHOD(SearchForUpdates_SucceededWithErrors)
|
||
{
|
||
auto factory = std::make_unique<MockWindowsUpdateClassFactory>();
|
||
auto* fp = factory.get();
|
||
fp->session->searcher->searchResult->resultCode = OperationResultCode::orcSucceededWithErrors;
|
||
AddMockUpdate(fp->session->searcher->searchResult->updates.get(), VARIANT_FALSE);
|
||
|
||
WindowsUpdateContext ctx(std::move(factory));
|
||
|
||
// orcSucceededWithErrors must succeed — the update count is still returned.
|
||
VERIFY_ARE_EQUAL(1u, ctx.SearchForUpdates());
|
||
}
|
||
|
||
TEST_METHOD(SearchForUpdates_Failed)
|
||
{
|
||
auto factory = std::make_unique<MockWindowsUpdateClassFactory>();
|
||
factory->session->searcher->searchResult->resultCode = OperationResultCode::orcFailed;
|
||
|
||
WindowsUpdateContext ctx(std::move(factory));
|
||
|
||
VERIFY_ARE_EQUAL(WSLC_E_WU_SEARCH_FAILED, CaptureHResult([&] { ctx.SearchForUpdates(); }));
|
||
}
|
||
|
||
// -----------------------------------------------------------------------
|
||
// DownloadUpdates tests
|
||
// -----------------------------------------------------------------------
|
||
|
||
TEST_METHOD(DownloadUpdates_AllAlreadyDownloaded)
|
||
{
|
||
auto factory = std::make_unique<MockWindowsUpdateClassFactory>();
|
||
auto* fp = factory.get();
|
||
auto* col = fp->session->searcher->searchResult->updates.get();
|
||
AddMockUpdate(col, VARIANT_TRUE);
|
||
AddMockUpdate(col, VARIANT_TRUE);
|
||
|
||
WindowsUpdateContext ctx(std::move(factory));
|
||
ctx.SearchForUpdates();
|
||
|
||
std::vector<uint32_t> progressCalls;
|
||
ctx.DownloadUpdates([&](uint32_t p) { progressCalls.push_back(p); });
|
||
|
||
// All updates were already downloaded: BeginDownload must not be called,
|
||
// and progress(100) must be reported to signal completion.
|
||
VERIFY_IS_FALSE(fp->session->downloader->beginDownloadCalled);
|
||
VERIFY_ARE_EQUAL(0L, fp->lastCreatedCollection->addCallCount);
|
||
VERIFY_ARE_EQUAL(1u, progressCalls.size());
|
||
VERIFY_ARE_EQUAL(100u, progressCalls[0]);
|
||
}
|
||
|
||
TEST_METHOD(DownloadUpdates_SomeNeedDownloading)
|
||
{
|
||
auto factory = std::make_unique<MockWindowsUpdateClassFactory>();
|
||
auto* fp = factory.get();
|
||
auto* col = fp->session->searcher->searchResult->updates.get();
|
||
AddMockUpdate(col, VARIANT_TRUE); // already downloaded — skip
|
||
AddMockUpdate(col, VARIANT_FALSE); // needs download
|
||
AddMockUpdate(col, VARIANT_FALSE); // needs download
|
||
|
||
WindowsUpdateContext ctx(std::move(factory));
|
||
ctx.SearchForUpdates();
|
||
ctx.DownloadUpdates();
|
||
|
||
VERIFY_IS_TRUE(fp->session->downloader->beginDownloadCalled);
|
||
VERIFY_ARE_EQUAL(2L, fp->lastCreatedCollection->addCallCount);
|
||
}
|
||
|
||
TEST_METHOD(DownloadUpdates_DownloadFails)
|
||
{
|
||
auto factory = std::make_unique<MockWindowsUpdateClassFactory>();
|
||
auto* fp = factory.get();
|
||
AddMockUpdate(fp->session->searcher->searchResult->updates.get(), VARIANT_FALSE);
|
||
fp->session->downloader->downloadResult->downloadHResult = E_FAIL;
|
||
|
||
WindowsUpdateContext ctx(std::move(factory));
|
||
ctx.SearchForUpdates();
|
||
|
||
VERIFY_ARE_EQUAL(E_FAIL, CaptureHResult([&] { ctx.DownloadUpdates(); }));
|
||
}
|
||
|
||
// -----------------------------------------------------------------------
|
||
// InstallUpdates tests
|
||
// -----------------------------------------------------------------------
|
||
|
||
TEST_METHOD(InstallUpdates_Success)
|
||
{
|
||
auto factory = std::make_unique<MockWindowsUpdateClassFactory>();
|
||
auto* fp = factory.get();
|
||
AddMockUpdate(fp->session->searcher->searchResult->updates.get(), VARIANT_TRUE);
|
||
|
||
WindowsUpdateContext ctx(std::move(factory));
|
||
ctx.SearchForUpdates();
|
||
|
||
// Should not throw.
|
||
ctx.InstallUpdates();
|
||
|
||
VERIFY_IS_TRUE(fp->session->installer->beginInstallCalled);
|
||
VERIFY_IS_NOT_NULL(fp->session->installer->capturedCollection.get());
|
||
}
|
||
|
||
TEST_METHOD(InstallUpdates_Fails)
|
||
{
|
||
auto factory = std::make_unique<MockWindowsUpdateClassFactory>();
|
||
auto* fp = factory.get();
|
||
AddMockUpdate(fp->session->searcher->searchResult->updates.get(), VARIANT_TRUE);
|
||
fp->session->installer->installResult->installHResult = E_ACCESSDENIED;
|
||
|
||
WindowsUpdateContext ctx(std::move(factory));
|
||
ctx.SearchForUpdates();
|
||
|
||
VERIFY_ARE_EQUAL(E_ACCESSDENIED, CaptureHResult([&] { ctx.InstallUpdates(); }));
|
||
}
|
||
|
||
// -----------------------------------------------------------------------
|
||
// RunUpdateFlow tests
|
||
// -----------------------------------------------------------------------
|
||
|
||
TEST_METHOD(RunUpdateFlow_NoUpdates_ProgressGoesTo100)
|
||
{
|
||
auto factory = std::make_unique<MockWindowsUpdateClassFactory>();
|
||
auto* fp = factory.get();
|
||
|
||
WindowsUpdateContext ctx(std::move(factory));
|
||
|
||
std::vector<uint32_t> progressCalls;
|
||
ctx.RunUpdateFlow(WindowsUpdateContext::UpdateOptions::None, [&](uint32_t p) { progressCalls.push_back(p); });
|
||
|
||
// progress(0) at the start, progress(100) because there are no updates.
|
||
VERIFY_ARE_EQUAL(2u, progressCalls.size());
|
||
VERIFY_ARE_EQUAL(0u, progressCalls[0]);
|
||
VERIFY_ARE_EQUAL(100u, progressCalls[1]);
|
||
|
||
// No download or install should have been triggered.
|
||
VERIFY_IS_FALSE(fp->session->downloader->beginDownloadCalled);
|
||
VERIFY_IS_FALSE(fp->session->installer->beginInstallCalled);
|
||
}
|
||
|
||
TEST_METHOD(RunUpdateFlow_UpdatesFound_DownloadThenInstall)
|
||
{
|
||
auto factory = std::make_unique<MockWindowsUpdateClassFactory>();
|
||
auto* fp = factory.get();
|
||
AddMockUpdate(fp->session->searcher->searchResult->updates.get(), VARIANT_FALSE);
|
||
|
||
WindowsUpdateContext ctx(std::move(factory));
|
||
|
||
std::vector<uint32_t> progressCalls;
|
||
ctx.RunUpdateFlow(WindowsUpdateContext::UpdateOptions::None, [&](uint32_t p) { progressCalls.push_back(p); });
|
||
|
||
// progress(0) is emitted at the start.
|
||
VERIFY_IS_FALSE(progressCalls.empty());
|
||
VERIFY_ARE_EQUAL(0u, progressCalls[0]);
|
||
|
||
// Both download and install phases ran.
|
||
VERIFY_IS_TRUE(fp->session->downloader->beginDownloadCalled);
|
||
VERIFY_IS_TRUE(fp->session->installer->beginInstallCalled);
|
||
}
|
||
|
||
TEST_METHOD(RunUpdateFlow_DownloadProgressScaling)
|
||
{
|
||
// Verify that download progress values are scaled into the 0–DownloadProgressPercent range.
|
||
// The download lambda is: progress((percent * DownloadProgressPercent) / 100)
|
||
// For percent=50: expected outer value = (50 * 70) / 100 = 35
|
||
VERIFY_ARE_EQUAL(35u, (50u * WindowsUpdateContext::DownloadProgressPercent) / 100u);
|
||
|
||
// Verify that install progress values are offset and scaled into the remaining range.
|
||
// The install lambda is: progress(DownloadProgressPercent + (percent * InstallProgressPercent) / 100)
|
||
// For percent=100: expected outer value = 70 + (100 * 30) / 100 = 100
|
||
VERIFY_ARE_EQUAL(100u, WindowsUpdateContext::DownloadProgressPercent + (100u * WindowsUpdateContext::InstallProgressPercent) / 100u);
|
||
}
|
||
|
||
TEST_METHOD(RunUpdateFlow_DownloadFails_Propagates)
|
||
{
|
||
auto factory = std::make_unique<MockWindowsUpdateClassFactory>();
|
||
auto* fp = factory.get();
|
||
AddMockUpdate(fp->session->searcher->searchResult->updates.get(), VARIANT_FALSE);
|
||
fp->session->downloader->downloadResult->downloadHResult = E_FAIL;
|
||
|
||
WindowsUpdateContext ctx(std::move(factory));
|
||
|
||
VERIFY_ARE_EQUAL(E_FAIL, CaptureHResult([&] { ctx.RunUpdateFlow(WindowsUpdateContext::UpdateOptions::None); }));
|
||
|
||
// Install should not have been called after download failure.
|
||
VERIFY_IS_FALSE(fp->session->installer->beginInstallCalled);
|
||
}
|
||
|
||
TEST_METHOD(RunUpdateFlow_NoProgress_DoesNotCrash)
|
||
{
|
||
// Verifies that passing no progress callback does not crash.
|
||
auto factory = std::make_unique<MockWindowsUpdateClassFactory>();
|
||
AddMockUpdate(factory->session->searcher->searchResult->updates.get(), VARIANT_TRUE);
|
||
|
||
WindowsUpdateContext ctx(std::move(factory));
|
||
|
||
// Should complete without crashing even with no progress callback.
|
||
ctx.RunUpdateFlow(WindowsUpdateContext::UpdateOptions::None);
|
||
}
|
||
};
|