Files
UnrealEngine/Engine/Source/Runtime/Online/BuildPatchServices/Private/Tests/Unit/DownloadService.spec.cpp
2025-05-18 13:04:45 +08:00

298 lines
9.6 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "Misc/AutomationTest.h"
#include "Tests/TestHelpers.h"
#include "Tests/Fake/HttpManager.fake.h"
#include "Tests/Mock/FileSystem.mock.h"
#include "Tests/Mock/DownloadServiceStat.mock.h"
#include "Tests/Mock/InstallerAnalytics.mock.h"
#include "Installer/DownloadService.h"
#include "Containers/Ticker.h"
#include "BuildPatchHash.h"
#if WITH_DEV_AUTOMATION_TESTS
BEGIN_DEFINE_SPEC(FDownloadServiceSpec, "BuildPatchServices.Unit", EAutomationTestFlags::ProductFilter | EAutomationTestFlags_ApplicationContextMask)
// Unit.
TUniquePtr<BuildPatchServices::IDownloadService> DownloadService;
// Mock/Fake.
FTSTicker Ticker;
TUniquePtr<BuildPatchServices::FFakeHttpManager> FakeHttpModule;
TUniquePtr<BuildPatchServices::FMockFileSystem> MockFileSystem;
TUniquePtr<BuildPatchServices::FMockDownloadServiceStat> MockDownloadServiceStat;
TUniquePtr<BuildPatchServices::FMockInstallerAnalytics> MockInstallerAnalytics;
// Data.
BuildPatchServices::FDownloadProgressDelegate DownloadProgress;
BuildPatchServices::FDownloadCompleteDelegate DownloadComplete;
TArray<TTuple<double, int32, int32>> RxDownloadProgress;
TArray<TTuple<double, int32, BuildPatchServices::FDownloadRef>> RxDownloadComplete;
FString HttpFileUrl;
FString HttpsFileUrl;
FString NetworkFileUrl;
int32 MadeRequestId;
// Ticker helpers.
void DoTick();
void DoTicksUntilCreated(int32 TickCount = 50, int32 CreateCount = 1, float Sleep = 0.0f);
void DoTicksUntilComplete(int32 TickCount = 50, int32 CompleteCount = 1, float Sleep = 0.0f);
END_DEFINE_SPEC(FDownloadServiceSpec)
void FDownloadServiceSpec::Define()
{
using namespace BuildPatchServices;
// Data setup.
FRollingHashConst::Init();
DownloadProgress.BindLambda([this](int32 RequestId, int32 BytesSoFar)
{
RxDownloadProgress.Emplace(FStatsCollector::GetSeconds(), RequestId, BytesSoFar);
});
DownloadComplete.BindLambda([this](int32 RequestId, const FDownloadRef& Download)
{
RxDownloadComplete.Emplace(FStatsCollector::GetSeconds(), RequestId, Download);
});
HttpFileUrl = TEXT("http://download.tests.com/file.dat");
HttpsFileUrl = TEXT("https://download.tests.com/file.dat");
NetworkFileUrl = TEXT("\\\\somenetwork\\somefolder\\file.dat");
// Specs.
BeforeEach([this]()
{
Ticker.Reset();
FakeHttpModule.Reset(new FFakeHttpManager(Ticker));
MockFileSystem.Reset(new FMockFileSystem());
MockDownloadServiceStat.Reset(new FMockDownloadServiceStat());
MockInstallerAnalytics.Reset(new FMockInstallerAnalytics());
DownloadService.Reset(FDownloadServiceFactory::Create(
FakeHttpModule.Get(),
MockFileSystem.Get(),
MockDownloadServiceStat.Get(),
MockInstallerAnalytics.Get()));
});
xDescribe("DownloadService", [this]()
{
Describe("RequestFile", [this]()
{
Describe("when given a FileUri starting with http", [this]()
{
It("should use the http module to process the request.", [this]()
{
DownloadService->RequestFile(HttpFileUrl, DownloadComplete, DownloadProgress);
DoTicksUntilComplete();
TEST_EQUAL(FakeHttpModule->RxCreateRequest, 1);
TEST_EQUAL(MockFileSystem->RxCreateFileReader.Num(), 0);
});
});
Describe("when given a FileUri starting with https", [this]()
{
It("should use the http module to process the request.", [this]()
{
DownloadService->RequestFile(HttpsFileUrl, DownloadComplete, DownloadProgress);
DoTicksUntilComplete();
TEST_EQUAL(FakeHttpModule->RxCreateRequest, 1);
TEST_EQUAL(MockFileSystem->RxCreateFileReader.Num(), 0);
});
});
Describe("when given a FileUri not starting with http", [this]()
{
xIt("should use the file manager to process the request.", [this]()
{
DownloadService->RequestFile(NetworkFileUrl, DownloadComplete, DownloadProgress);
DoTicksUntilComplete(50, 1);
TEST_EQUAL(FakeHttpModule->RxCreateRequest, 0);
TEST_EQUAL(MockFileSystem->RxCreateFileReader.Num(), 1);
});
});
Describe("when the http request results in success", [this]()
{
BeforeEach([this]()
{
FakeHttpModule->DataServed.Add(HttpFileUrl, {1,2,3,4,5,6,7,8,9,10});
});
It("should provide an IDownload with access to success status.", [this]()
{
DownloadService->RequestFile(HttpFileUrl, DownloadComplete, DownloadProgress);
DoTicksUntilComplete();
TEST_EQUAL(RxDownloadComplete.Num(), 1);
if (RxDownloadComplete.Num() == 1)
{
const FDownloadRef& Download = RxDownloadComplete[0].Get<2>();
TEST_TRUE(Download->ResponseSuccessful());
TEST_EQUAL(Download->GetResponseCode(), EHttpResponseCodes::Ok);
TEST_EQUAL(Download->GetData(), FakeHttpModule->DataServed[HttpFileUrl]);
}
});
});
Describe("when the file request results in success", [this]()
{
BeforeEach([this]()
{
MockFileSystem->ReadFile = {1,2,3,4,5,6,7,8,9,10};
});
xIt("should provide an IDownload with access to success status.", [this]()
{
DownloadService->RequestFile(NetworkFileUrl, DownloadComplete, DownloadProgress);
DoTicksUntilComplete();
TEST_EQUAL(RxDownloadComplete.Num(), 1);
if (RxDownloadComplete.Num() == 1)
{
const FDownloadRef& Download = RxDownloadComplete[0].Get<2>();
TEST_TRUE(Download->ResponseSuccessful());
TEST_EQUAL(Download->GetResponseCode(), EHttpResponseCodes::Ok);
TEST_EQUAL(Download->GetData(), MockFileSystem->ReadFile);
}
});
});
});
Describe("RequestCancel", [this]()
{
Describe("when a file request was made", [this]()
{
BeforeEach([this]()
{
MockFileSystem->ReadFile.AddUninitialized(1024 * 1024 * 50);
MadeRequestId = DownloadService->RequestFile(NetworkFileUrl, DownloadComplete, DownloadProgress);
});
It("should cancel if it was not started yet.", [this]()
{
DownloadService->RequestCancel(MadeRequestId);
DoTicksUntilComplete();
TEST_EQUAL(FakeHttpModule->RxCreateRequest, 0);
TEST_EQUAL(MockFileSystem->RxCreateFileReader.Num(), 0);
TEST_EQUAL(RxDownloadComplete.Num(), 1);
if (RxDownloadComplete.Num() == 1)
{
const FDownloadRef& Download = RxDownloadComplete[0].Get<2>();
TEST_FALSE(Download->ResponseSuccessful());
}
});
xIt("should cancel if it had already started.", [this]()
{
DoTicksUntilCreated();
DownloadService->RequestCancel(MadeRequestId);
DoTicksUntilComplete(50, 1, 0.1f);
TEST_EQUAL(FakeHttpModule->RxCreateRequest, 0);
TEST_EQUAL(MockFileSystem->RxCreateFileReader.Num(), 1);
TEST_EQUAL(RxDownloadComplete.Num(), 1);
if (RxDownloadComplete.Num() == 1)
{
const FDownloadRef& Download = RxDownloadComplete[0].Get<2>();
TEST_FALSE(Download->ResponseSuccessful());
}
});
});
Describe("when a HTTP request was made", [this]()
{
BeforeEach([this]()
{
FakeHttpModule->DataServed.FindOrAdd(HttpFileUrl).AddUninitialized(1024*1024*50);
MadeRequestId = DownloadService->RequestFile(HttpFileUrl, DownloadComplete, DownloadProgress);
});
It("should cancel if it was not started yet.", [this]()
{
DownloadService->RequestCancel(MadeRequestId);
DoTicksUntilComplete();
TEST_EQUAL(FakeHttpModule->RxCreateRequest, 0);
TEST_EQUAL(MockFileSystem->RxCreateFileReader.Num(), 0);
TEST_EQUAL(RxDownloadComplete.Num(), 1);
if (RxDownloadComplete.Num() == 1)
{
const FDownloadRef& Download = RxDownloadComplete[0].Get<2>();
TEST_FALSE(Download->ResponseSuccessful());
}
});
It("should cancel if it had already started.", [this]()
{
DoTicksUntilCreated();
DownloadService->RequestCancel(MadeRequestId);
DoTicksUntilComplete();
TEST_EQUAL(FakeHttpModule->RxCreateRequest, 1);
TEST_EQUAL(MockFileSystem->RxCreateFileReader.Num(), 0);
TEST_EQUAL(RxDownloadComplete.Num(), 1);
if (RxDownloadComplete.Num() == 1)
{
const FDownloadRef& Download = RxDownloadComplete[0].Get<2>();
TEST_FALSE(Download->ResponseSuccessful());
}
});
});
});
Describe("Destructor", [this]()
{
Describe("when there are currently active http requests", [this]()
{
It("should cancel the request.", [this]()
{
int32 RequestId = DownloadService->RequestFile(HttpFileUrl, DownloadComplete, DownloadProgress);
DoTicksUntilCreated();
DownloadService.Reset();
DoTicksUntilComplete();
TEST_EQUAL(FakeHttpModule->RxCreateRequest, 1);
TEST_EQUAL(MockFileSystem->RxCreateFileReader.Num(), 0);
TEST_EQUAL(RxDownloadComplete.Num(), 1);
if (RxDownloadComplete.Num() == 1)
{
const FDownloadRef& Download = RxDownloadComplete[0].Get<2>();
TEST_FALSE(Download->ResponseSuccessful());
}
});
});
});
});
AfterEach([this]()
{
DownloadService.Reset();
RxDownloadProgress.Reset();
RxDownloadComplete.Reset();
FakeHttpModule.Reset();
MockFileSystem.Reset();
MockDownloadServiceStat.Reset();
MockInstallerAnalytics.Reset();
});
}
void FDownloadServiceSpec::DoTick()
{
Ticker.Tick(0.1f);
}
void FDownloadServiceSpec::DoTicksUntilComplete(int32 TickCount, int32 CompleteCount, float Sleep)
{
while (TickCount > 0 && RxDownloadComplete.Num() < CompleteCount)
{
DoTick();
--TickCount;
FPlatformProcess::Sleep(Sleep);
}
}
void FDownloadServiceSpec::DoTicksUntilCreated(int32 TickCount, int32 CreateCount, float Sleep)
{
MockFileSystem->ThreadLock.Lock();
while (TickCount > 0 && (FakeHttpModule->RxCreateRequest + MockFileSystem->RxCreateFileReader.Num()) < CreateCount)
{
MockFileSystem->ThreadLock.Unlock();
DoTick();
--TickCount;
FPlatformProcess::Sleep(Sleep);
MockFileSystem->ThreadLock.Lock();
}
MockFileSystem->ThreadLock.Unlock();
}
#endif //WITH_DEV_AUTOMATION_TESTS