// Copyright Epic Games, Inc. All Rights Reserved. #include "Misc/AutomationTest.h" #include "Tests/TestHelpers.h" #include "Core/BlockStructure.h" #if WITH_DEV_AUTOMATION_TESTS namespace { template TArray operator+(const TArray& LHS, const TArray& RHS) { TArray Result; Result.Empty(LHS.Num() + RHS.Num()); Result.Append(LHS); Result.Append(RHS); return Result; } template TArray ArrayLeft(const TArray& Array, int32 Count) { TArray Result; Result.Empty(Count); Result.Append(Array.GetData(), Count); return Result; } template TArray ArrayRight(const TArray& Array, int32 Count) { TArray Result; Result.Empty(Count); Result.Append(Array.GetData() + (Array.Num() - Count), Count); return Result; } template TArray ArrayChopLeft(const TArray& Array, int32 Count) { TArray Result; Result.Empty(Array.Num() - Count); Result.Append(Array.GetData() + Count, Array.Num() - Count); return Result; } template TArray ArrayChopRight(const TArray& Array, int32 Count) { TArray Result; Result.Empty(Array.Num() - Count); Result.Append(Array.GetData(), Array.Num() - Count); return Result; } template TArray ArrayHead(const TArray& Array) { return ArrayLeft(Array, 2); } template TArray ArrayTail(const TArray& Array) { return ArrayRight(Array, 2); } template TArray ArrayClobber(const TArray& Array, int32 Start, const TArray& Elements) { TArray Result; int32 NewLength = FMath::Max(Array.Num(), Start + (Elements.Num() - 1)); Result.Empty(NewLength); Result.Append(Array); for (int32 ElementIdx = 0; ElementIdx < Elements.Num(); ++ElementIdx) { Result[Start + ElementIdx] = Elements[ElementIdx]; } return Result; } } BEGIN_DEFINE_SPEC(FBlockStructureSpec, "BuildPatchServices.Unit", EAutomationTestFlags::ProductFilter | EAutomationTestFlags_ApplicationContextMask) // Unit. BuildPatchServices::FBlockStructure BlockStructure; BuildPatchServices::FBlockStructure OtherStructure; // Data. TArray DirNames; TArray DirTypes; TArray SetupBlocks; // Test helpers. bool IsLooping(const BuildPatchServices::FBlockStructure& Structure); bool IsMirrored(const BuildPatchServices::FBlockStructure& Structure); TArray ToArrayU64(const BuildPatchServices::FBlockStructure& Structure); TSet EnumeratePtrs(const BuildPatchServices::FBlockStructure& Structure); END_DEFINE_SPEC(FBlockStructureSpec) void FBlockStructureSpec::Define() { using namespace BuildPatchServices; // Data setup. DirNames = {TEXT("FromStart"), TEXT("FromEnd")}; DirTypes = {ESearchDir::FromStart, ESearchDir::FromEnd}; SetupBlocks = {10, 5, 20, 7, 30, 3, 40, 5}; // Specs. BeforeEach([this]() { BlockStructure = FBlockStructure(); OtherStructure = FBlockStructure(); }); Describe("BlockStructure", [this]() { Describe("Copy Constructor", [this]() { Describe("when copying an empty structure", [this]() { It("should create an empty structure.", [this]() { FBlockStructure NewBlockStructure(BlockStructure); TEST_NULL(NewBlockStructure.GetHead()); TEST_NULL(NewBlockStructure.GetTail()); }); }); Describe("when some blocks already exist", [this]() { BeforeEach([this]() { for (int32 Idx = 0; Idx < SetupBlocks.Num(); Idx += 2) { BlockStructure.Add(SetupBlocks[Idx], SetupBlocks[Idx + 1]); } }); It("should create an equal structure.", [this]() { FBlockStructure NewBlockStructure(BlockStructure); TEST_EQUAL(ToArrayU64(BlockStructure), ToArrayU64(NewBlockStructure)); }); It("should not share memory.", [this]() { FBlockStructure NewBlockStructure(BlockStructure); TEST_TRUE(EnumeratePtrs(NewBlockStructure).Intersect(EnumeratePtrs(BlockStructure)).Num() == 0); }); }); }); Describe("Move Constructor", [this]() { Describe("when currently an empty structure", [this]() { It("should create an empty structure.", [this]() { FBlockStructure NewBlockStructure(MoveTemp(BlockStructure)); TEST_NULL(NewBlockStructure.GetHead()); TEST_NULL(NewBlockStructure.GetTail()); }); It("should leave source as an empty structure.", [this]() { FBlockStructure NewBlockStructure(MoveTemp(BlockStructure)); TEST_NULL(BlockStructure.GetHead()); TEST_NULL(BlockStructure.GetTail()); }); }); Describe("when some blocks already exist", [this]() { BeforeEach([this]() { for (int32 Idx = 0; Idx < SetupBlocks.Num(); Idx += 2) { BlockStructure.Add(SetupBlocks[Idx], SetupBlocks[Idx + 1]); } }); It("should create a structure with same data.", [this]() { const FBlockEntry* HeadPtr = BlockStructure.GetHead(); const FBlockEntry* TailPtr = BlockStructure.GetTail(); FBlockStructure NewBlockStructure(MoveTemp(BlockStructure)); TEST_EQUAL(NewBlockStructure.GetHead(), HeadPtr); TEST_EQUAL(NewBlockStructure.GetTail(), TailPtr); TEST_EQUAL(ToArrayU64(NewBlockStructure), SetupBlocks); }); It("should leave source as an empty structure.", [this]() { FBlockStructure NewBlockStructure(MoveTemp(BlockStructure)); TEST_NULL(BlockStructure.GetHead()); TEST_NULL(BlockStructure.GetTail()); }); }); }); Describe("Copy Assignment Operator", [this]() { BeforeEach([this]() { OtherStructure.Add(5, 7); OtherStructure.Add(19, 12); OtherStructure.Add(42, 2); }); Describe("when copying an empty structure", [this]() { It("should create an empty structure.", [this]() { OtherStructure = BlockStructure; TEST_NULL(OtherStructure.GetHead()); TEST_NULL(OtherStructure.GetTail()); }); }); Describe("when some blocks already exist", [this]() { BeforeEach([this]() { for (int32 Idx = 0; Idx < SetupBlocks.Num(); Idx += 2) { BlockStructure.Add(SetupBlocks[Idx], SetupBlocks[Idx + 1]); } }); It("should create an equal structure.", [this]() { OtherStructure = BlockStructure; TEST_EQUAL(ToArrayU64(OtherStructure), ToArrayU64(BlockStructure)); }); It("should not share memory.", [this]() { OtherStructure = BlockStructure; TEST_TRUE(EnumeratePtrs(OtherStructure).Intersect(EnumeratePtrs(BlockStructure)).Num() == 0); }); }); }); Describe("Move Assignment Operator", [this]() { BeforeEach([this]() { OtherStructure.Add(5, 7); OtherStructure.Add(19, 12); OtherStructure.Add(42, 2); }); Describe("when moving from an empty structure", [this]() { It("should become an empty structure.", [this]() { OtherStructure = MoveTemp(BlockStructure); TEST_NULL(OtherStructure.GetHead()); TEST_NULL(OtherStructure.GetTail()); }); It("should leave source as an empty structure.", [this]() { OtherStructure = MoveTemp(BlockStructure); TEST_NULL(BlockStructure.GetHead()); TEST_NULL(BlockStructure.GetTail()); }); }); Describe("when moving from a non-empty structure", [this]() { BeforeEach([this]() { for (int32 Idx = 0; Idx < SetupBlocks.Num(); Idx += 2) { BlockStructure.Add(SetupBlocks[Idx], SetupBlocks[Idx + 1]); } }); It("should take memory ownership.", [this]() { const FBlockEntry* HeadPtr = BlockStructure.GetHead(); const FBlockEntry* TailPtr = BlockStructure.GetTail(); OtherStructure = MoveTemp(BlockStructure); TEST_EQUAL(OtherStructure.GetHead(), HeadPtr); TEST_EQUAL(OtherStructure.GetTail(), TailPtr); }); It("should become a structure with same data.", [this]() { OtherStructure = MoveTemp(BlockStructure); TEST_EQUAL(ToArrayU64(OtherStructure), SetupBlocks); }); It("should make source an empty structure.", [this]() { OtherStructure = MoveTemp(BlockStructure); TEST_NULL(BlockStructure.GetHead()); TEST_NULL(BlockStructure.GetTail()); }); }); }); Describe("GetHead", [this]() { Describe("when currently an empty structure", [this]() { It("should return nullptr.", [this]() { const FBlockEntry* HeadPtr = BlockStructure.GetHead(); TEST_NULL(HeadPtr); }); }); Describe("when some blocks already exist", [this]() { BeforeEach([this]() { for (int32 Idx = 0; Idx < SetupBlocks.Num(); Idx += 2) { BlockStructure.Add(SetupBlocks[Idx], SetupBlocks[Idx + 1]); } }); It("should return valid ptr.", [this]() { TEST_NOT_NULL(BlockStructure.GetHead()); }); It("should return the ptr to the head block.", [this]() { const FBlockEntry* HeadPtr = BlockStructure.GetHead(); if (HeadPtr != nullptr) { TEST_EQUAL(ARRAYU64(HeadPtr->GetOffset(), HeadPtr->GetSize()), ArrayHead(SetupBlocks)); } }); }); }); Describe("GetTail", [this]() { Describe("when currently an empty structure", [this]() { It("should return nullptr.", [this]() { const FBlockEntry* TailPtr = BlockStructure.GetTail(); TEST_NULL(TailPtr); }); }); Describe("when some blocks already exist", [this]() { BeforeEach([this]() { for (int32 Idx = 0; Idx < SetupBlocks.Num(); Idx += 2) { BlockStructure.Add(SetupBlocks[Idx], SetupBlocks[Idx + 1]); } }); It("should return valid ptr.", [this]() { TEST_NOT_NULL(BlockStructure.GetTail()); }); It("should return the ptr to the head block.", [this]() { const FBlockEntry* TailPtr = BlockStructure.GetTail(); if (TailPtr != nullptr) { TEST_EQUAL(ARRAYU64(TailPtr->GetOffset(), TailPtr->GetSize()), ArrayTail(SetupBlocks)); } }); }); }); Describe("Empty", [this]() { Describe("when currently an empty structure", [this]() { It("should stay as an empty structure.", [this]() { BlockStructure.Empty(); TEST_NULL(BlockStructure.GetHead()); TEST_NULL(BlockStructure.GetTail()); }); }); Describe("when some blocks already exist", [this]() { BeforeEach([this]() { for (int32 Idx = 0; Idx < SetupBlocks.Num(); Idx += 2) { BlockStructure.Add(SetupBlocks[Idx], SetupBlocks[Idx + 1]); } }); It("should become an empty structure.", [this]() { BlockStructure.Empty(); TEST_NULL(BlockStructure.GetHead()); TEST_NULL(BlockStructure.GetTail()); }); }); }); Describe("Add", [this]() { for (int32 DirIdx = 0; DirIdx < DirTypes.Num(); ++DirIdx) { ESearchDir::Type Dir = DirTypes[DirIdx]; Describe(FString::Printf(TEXT("when called with %s"), *DirNames[DirIdx]), [this, Dir]() { Describe("when currently an empty structure", [this, Dir]() { Describe("when adding a single block", [this, Dir]() { It("should result in containing the single block.", [this, Dir]() { BlockStructure.Add(70, 10, Dir); TEST_EQUAL(ToArrayU64(BlockStructure), ARRAYU64(70, 10)); }); It("should stay empty when adding zero.", [this, Dir]() { BlockStructure.Add(70, 0, Dir); TEST_NULL(BlockStructure.GetHead()); TEST_NULL(BlockStructure.GetTail()); }); }); Describe("when adding multiple blocks", [this, Dir]() { It("should result in containing each of the blocks.", [this, Dir]() { for (int32 Idx = 0; Idx < SetupBlocks.Num(); Idx += 2) { BlockStructure.Add(SetupBlocks[Idx], SetupBlocks[Idx + 1], Dir); } TEST_EQUAL(ToArrayU64(BlockStructure), SetupBlocks); }); It("should combine right adjacent blocks.", [this, Dir]() { BlockStructure.Add(10, 7, Dir); BlockStructure.Add(17, 5, Dir); BlockStructure.Add(22, 9, Dir); TEST_EQUAL(ToArrayU64(BlockStructure), ARRAYU64(10, 21)); }); It("should combine left adjacent blocks.", [this, Dir]() { BlockStructure.Add(22, 9, Dir); BlockStructure.Add(17, 5, Dir); BlockStructure.Add(10, 7, Dir); TEST_EQUAL(ToArrayU64(BlockStructure), ARRAYU64(10, 21)); }); }); Describe("when adding another structure", [this, Dir]() { BeforeEach([this, Dir]() { for (int32 Idx = 0; Idx < SetupBlocks.Num(); Idx += 2) { OtherStructure.Add(SetupBlocks[Idx], SetupBlocks[Idx + 1], Dir); } }); It("should result in the same structure.", [this, Dir]() { BlockStructure.Add(OtherStructure, Dir); TEST_EQUAL(ToArrayU64(BlockStructure), ToArrayU64(OtherStructure)); }); }); }); Describe("when some blocks already exist", [this, Dir]() { BeforeEach([this, Dir]() { for (int32 Idx = 0; Idx < SetupBlocks.Num(); Idx += 2) { BlockStructure.Add(SetupBlocks[Idx], SetupBlocks[Idx + 1], Dir); } }); It("should ignore zero size block left of head.", [this, Dir]() { BlockStructure.Add(0, 0, Dir); TEST_EQUAL(ToArrayU64(BlockStructure), SetupBlocks); }); It("should ignore zero size block right of tail.", [this, Dir]() { BlockStructure.Add(100, 0, Dir); TEST_EQUAL(ToArrayU64(BlockStructure), SetupBlocks); }); It("should ignore zero size block in empty space.", [this, Dir]() { BlockStructure.Add(28, 0, Dir); TEST_EQUAL(ToArrayU64(BlockStructure), SetupBlocks); }); It("should insert a new head.", [this, Dir]() { BlockStructure.Add(0, 6, Dir); TEST_EQUAL(ToArrayU64(BlockStructure), ARRAYU64(0, 6) + SetupBlocks); }); It("should grow left an existing head with adjacent block.", [this, Dir]() { BlockStructure.Add(8, 2, Dir); TEST_EQUAL(ToArrayU64(BlockStructure), ArrayClobber(SetupBlocks, 0, ARRAYU64(8, 7))); }); It("should grow left an existing head with partial overlapped block.", [this, Dir]() { BlockStructure.Add(8, 4, Dir); TEST_EQUAL(ToArrayU64(BlockStructure), ArrayClobber(SetupBlocks, 0, ARRAYU64(8, 7))); }); It("should grow left an existing head with fully overlapped block.", [this, Dir]() { BlockStructure.Add(8, 7, Dir); TEST_EQUAL(ToArrayU64(BlockStructure), ArrayClobber(SetupBlocks, 0, ARRAYU64(8, 7))); }); It("should grow right an existing head with adjacent block.", [this, Dir]() { BlockStructure.Add(15, 4, Dir); TEST_EQUAL(ToArrayU64(BlockStructure), ArrayClobber(SetupBlocks, 0, ARRAYU64(10, 9))); }); It("should grow right an existing head with partial overlapped block.", [this, Dir]() { BlockStructure.Add(12, 7, Dir); TEST_EQUAL(ToArrayU64(BlockStructure), ArrayClobber(SetupBlocks, 0, ARRAYU64(10, 9))); }); It("should grow right an existing head with fully overlapped block.", [this, Dir]() { BlockStructure.Add(10, 9, Dir); TEST_EQUAL(ToArrayU64(BlockStructure), ArrayClobber(SetupBlocks, 0, ARRAYU64(10, 9))); }); It("should grow outwards an existing head with fully overlapped block.", [this, Dir]() { BlockStructure.Add(8, 9, Dir); TEST_EQUAL(ToArrayU64(BlockStructure), ArrayClobber(SetupBlocks, 0, ARRAYU64(8, 9))); }); It("should combine an existing head with second block when exactly filling the gap.", [this, Dir]() { BlockStructure.Add(15, 5, Dir); TEST_EQUAL(ToArrayU64(BlockStructure), ARRAYU64(10, 17) + ArrayChopLeft(SetupBlocks, 4)); }); It("should combine an existing head with second block when overlapping the gap.", [this, Dir]() { BlockStructure.Add(13, 9, Dir); TEST_EQUAL(ToArrayU64(BlockStructure), ARRAYU64(10, 17) + ArrayChopLeft(SetupBlocks, 4)); }); It("should swallow a block enclosed in existing head from left edge to inside.", [this, Dir]() { BlockStructure.Add(10, 2, Dir); TEST_EQUAL(ToArrayU64(BlockStructure), SetupBlocks); }); It("should swallow a block enclosed in existing head from inside to right edge.", [this, Dir]() { BlockStructure.Add(12, 3, Dir); TEST_EQUAL(ToArrayU64(BlockStructure), SetupBlocks); }); It("should swallow a block fully enclosed in existing head.", [this, Dir]() { BlockStructure.Add(11, 3, Dir); TEST_EQUAL(ToArrayU64(BlockStructure), SetupBlocks); }); It("should swallow a block perfectly matching an existing head.", [this, Dir]() { BlockStructure.Add(10, 5, Dir); TEST_EQUAL(ToArrayU64(BlockStructure), SetupBlocks); }); It("should insert a new tail.", [this, Dir]() { BlockStructure.Add(50, 6, Dir); TEST_EQUAL(ToArrayU64(BlockStructure), SetupBlocks + ARRAYU64(50, 6)); }); It("should grow left an existing tail with adjacent block.", [this, Dir]() { BlockStructure.Add(38, 2, Dir); TEST_EQUAL(ToArrayU64(BlockStructure), ArrayClobber(SetupBlocks, SetupBlocks.Num() - 2, ARRAYU64(38, 7))); }); It("should grow left an existing tail with partial overlapped block.", [this, Dir]() { BlockStructure.Add(38, 4, Dir); TEST_EQUAL(ToArrayU64(BlockStructure), ArrayClobber(SetupBlocks, SetupBlocks.Num() - 2, ARRAYU64(38, 7))); }); It("should grow left an existing tail with fully overlapped block.", [this, Dir]() { BlockStructure.Add(38, 7, Dir); TEST_EQUAL(ToArrayU64(BlockStructure), ArrayClobber(SetupBlocks, SetupBlocks.Num() - 2, ARRAYU64(38, 7))); }); It("should grow right an existing tail with adjacent block.", [this, Dir]() { BlockStructure.Add(45, 4, Dir); TEST_EQUAL(ToArrayU64(BlockStructure), ArrayClobber(SetupBlocks, SetupBlocks.Num() - 2, ARRAYU64(40, 9))); }); It("should grow right an existing tail with partial overlapped block.", [this, Dir]() { BlockStructure.Add(42, 7, Dir); TEST_EQUAL(ToArrayU64(BlockStructure), ArrayClobber(SetupBlocks, SetupBlocks.Num() - 2, ARRAYU64(40, 9))); }); It("should grow right an existing tail with fully overlapped block.", [this, Dir]() { BlockStructure.Add(40, 9, Dir); TEST_EQUAL(ToArrayU64(BlockStructure), ArrayClobber(SetupBlocks, SetupBlocks.Num() - 2, ARRAYU64(40, 9))); }); It("should grow outwards an existing tail with fully overlapped block.", [this, Dir]() { BlockStructure.Add(38, 9, Dir); TEST_EQUAL(ToArrayU64(BlockStructure), ArrayClobber(SetupBlocks, SetupBlocks.Num() - 2, ARRAYU64(38, 9))); }); It("should combine an existing tail with second last block when exactly filling the gap.", [this, Dir]() { BlockStructure.Add(33, 7, Dir); TEST_EQUAL(ToArrayU64(BlockStructure), ArrayChopRight(SetupBlocks, 4) + ARRAYU64(30, 15)); }); It("should combine an existing tail with second last block when overlapping the gap.", [this, Dir]() { BlockStructure.Add(31, 11, Dir); TEST_EQUAL(ToArrayU64(BlockStructure), ArrayChopRight(SetupBlocks, 4) + ARRAYU64(30, 15)); }); It("should swallow a block enclosed in existing tail from left edge to inside.", [this, Dir]() { BlockStructure.Add(40, 2, Dir); TEST_EQUAL(ToArrayU64(BlockStructure), SetupBlocks); }); It("should swallow a block enclosed in existing tail from inside to right edge.", [this, Dir]() { BlockStructure.Add(42, 3, Dir); TEST_EQUAL(ToArrayU64(BlockStructure), SetupBlocks); }); It("should swallow a block fully enclosed in existing tail.", [this, Dir]() { BlockStructure.Add(41, 3, Dir); TEST_EQUAL(ToArrayU64(BlockStructure), SetupBlocks); }); It("should swallow a block perfectly matching an existing tail.", [this, Dir]() { BlockStructure.Add(40, 5, Dir); TEST_EQUAL(ToArrayU64(BlockStructure), SetupBlocks); }); It("should insert a new middle block.", [this, Dir]() { BlockStructure.Add(16, 2, Dir); TEST_EQUAL(ToArrayU64(BlockStructure), ArrayHead(SetupBlocks) + ARRAYU64(16, 2) + ArrayChopLeft(SetupBlocks, 2)); }); It("should grow left an existing middle block with adjacent block.", [this, Dir]() { BlockStructure.Add(18, 2, Dir); TEST_EQUAL(ToArrayU64(BlockStructure), ArrayClobber(SetupBlocks, 2, ARRAYU64(18, 9))); }); It("should grow left an existing middle block with partial overlapped block.", [this, Dir]() { BlockStructure.Add(18, 3, Dir); TEST_EQUAL(ToArrayU64(BlockStructure), ArrayClobber(SetupBlocks, 2, ARRAYU64(18, 9))); }); It("should grow left an existing middle block with fully overlapped block.", [this, Dir]() { BlockStructure.Add(18, 9, Dir); TEST_EQUAL(ToArrayU64(BlockStructure), ArrayClobber(SetupBlocks, 2, ARRAYU64(18, 9))); }); It("should grow right an existing middle block with adjacent block.", [this, Dir]() { BlockStructure.Add(27, 2, Dir); TEST_EQUAL(ToArrayU64(BlockStructure), ArrayClobber(SetupBlocks, 2, ARRAYU64(20, 9))); }); It("should grow right an existing middle block with partial overlapped block.", [this, Dir]() { BlockStructure.Add(26, 3, Dir); TEST_EQUAL(ToArrayU64(BlockStructure), ArrayClobber(SetupBlocks, 2, ARRAYU64(20, 9))); }); It("should grow right an existing middle block with fully overlapped block.", [this, Dir]() { BlockStructure.Add(20, 9, Dir); TEST_EQUAL(ToArrayU64(BlockStructure), ArrayClobber(SetupBlocks, 2, ARRAYU64(20, 9))); }); It("should grow outwards an existing middle block with fully overlapped block.", [this, Dir]() { BlockStructure.Add(18, 11, Dir); TEST_EQUAL(ToArrayU64(BlockStructure), ArrayClobber(SetupBlocks, 2, ARRAYU64(18, 11))); }); It("should combine existing blocks when exactly filling the gap.", [this, Dir]() { BlockStructure.Add(27, 3, Dir); TEST_EQUAL(ToArrayU64(BlockStructure), ArrayHead(SetupBlocks) + ARRAYU64(20, 13) + ArrayTail(SetupBlocks)); }); It("should combine existing blocks when overlapping the gap.", [this, Dir]() { BlockStructure.Add(26, 5, Dir); TEST_EQUAL(ToArrayU64(BlockStructure), ArrayHead(SetupBlocks) + ARRAYU64(20, 13) + ArrayTail(SetupBlocks)); }); It("should swallow a block enclosed in existing middle block from left edge to inside.", [this, Dir]() { BlockStructure.Add(20, 2, Dir); TEST_EQUAL(ToArrayU64(BlockStructure), SetupBlocks); }); It("should swallow a block enclosed in existing middle block from inside to right edge.", [this, Dir]() { BlockStructure.Add(24, 3, Dir); TEST_EQUAL(ToArrayU64(BlockStructure), SetupBlocks); }); It("should swallow a block fully enclosed in existing middle block.", [this, Dir]() { BlockStructure.Add(21, 5, Dir); TEST_EQUAL(ToArrayU64(BlockStructure), SetupBlocks); }); It("should swallow a block perfectly matching an existing middle block.", [this, Dir]() { BlockStructure.Add(20, 7, Dir); TEST_EQUAL(ToArrayU64(BlockStructure), SetupBlocks); }); It("should combine blocks when left of head up to edge of middle block.", [this, Dir]() { BlockStructure.Add(5, 25, Dir); TEST_EQUAL(ToArrayU64(BlockStructure), ARRAYU64(5, 28) + ArrayTail(SetupBlocks)); }); It("should combine blocks when start of head up to edge of middle block.", [this, Dir]() { BlockStructure.Add(10, 20, Dir); TEST_EQUAL(ToArrayU64(BlockStructure), ARRAYU64(10, 23) + ArrayTail(SetupBlocks)); }); It("should combine blocks when inside of head up to edge of middle block.", [this, Dir]() { BlockStructure.Add(12, 18, Dir); TEST_EQUAL(ToArrayU64(BlockStructure), ARRAYU64(10, 23) + ArrayTail(SetupBlocks)); }); It("should combine blocks when left of second block up to right of second last block.", [this, Dir]() { BlockStructure.Add(16, 23, Dir); TEST_EQUAL(ToArrayU64(BlockStructure), ArrayHead(SetupBlocks) + ARRAYU64(16, 23) + ArrayTail(SetupBlocks)); }); It("should combine blocks when start of second block up to end of second last block.", [this, Dir]() { BlockStructure.Add(20, 13, Dir); TEST_EQUAL(ToArrayU64(BlockStructure), ArrayHead(SetupBlocks) + ARRAYU64(20, 13) + ArrayTail(SetupBlocks)); }); It("should combine blocks when inside of second block up to inside of second last block.", [this, Dir]() { BlockStructure.Add(22, 10, Dir); TEST_EQUAL(ToArrayU64(BlockStructure), ArrayHead(SetupBlocks) + ARRAYU64(20, 13) + ArrayTail(SetupBlocks)); }); It("should combine blocks when end of second block up to start of second last block.", [this, Dir]() { BlockStructure.Add(27, 3, Dir); TEST_EQUAL(ToArrayU64(BlockStructure), ArrayHead(SetupBlocks) + ARRAYU64(20, 13) + ArrayTail(SetupBlocks)); }); It("should combine blocks when overlapping whole structure.", [this, Dir]() { BlockStructure.Add(5, 45, Dir); TEST_EQUAL(ToArrayU64(BlockStructure), ARRAYU64(5, 45)); }); Describe("when adding another structure the same", [this, Dir]() { BeforeEach([this, Dir]() { for (int32 Idx = 0; Idx < SetupBlocks.Num(); Idx += 2) { OtherStructure.Add(SetupBlocks[Idx], SetupBlocks[Idx + 1], Dir); } }); It("should result in the same structure.", [this, Dir]() { BlockStructure.Add(OtherStructure, Dir); TEST_EQUAL(ToArrayU64(BlockStructure), SetupBlocks); }); }); Describe("when adding a different structure with no overlap", [this, Dir]() { BeforeEach([this, Dir]() { OtherStructure.Add(5, 3, Dir); OtherStructure.Add(16, 3, Dir); OtherStructure.Add(28, 1, Dir); OtherStructure.Add(35, 4, Dir); OtherStructure.Add(50, 6, Dir); }); It("should result in the combined structure.", [this, Dir]() { BlockStructure.Add(OtherStructure, Dir); TEST_EQUAL(ToArrayU64(BlockStructure), ARRAYU64(5, 3, 10, 5, 16, 3, 20, 7, 28, 1, 30, 3, 35, 4, 40, 5, 50, 6)); }); }); Describe("when adding a different structure with some overlap", [this, Dir]() { BeforeEach([this, Dir]() { OtherStructure.Add(5, 5, Dir); OtherStructure.Add(19, 9, Dir); OtherStructure.Add(33, 7, Dir); }); It("should result in the combined structure.", [this, Dir]() { BlockStructure.Add(OtherStructure, Dir); TEST_EQUAL(ToArrayU64(BlockStructure), ARRAYU64(5, 10, 19, 9, 30, 15)); }); }); }); }); } }); Describe("Remove", [this]() { for (int32 DirIdx = 0; DirIdx < DirTypes.Num(); ++DirIdx) { ESearchDir::Type Dir = DirTypes[DirIdx]; Describe(FString::Printf(TEXT("when called with %s"), *DirNames[DirIdx]), [this, Dir]() { Describe("when currently an empty structure", [this, Dir]() { Describe("when removing a block", [this, Dir]() { It("should result in empty structure.", [this, Dir]() { BlockStructure.Remove(70, 0, Dir); TEST_NULL(BlockStructure.GetHead()); TEST_NULL(BlockStructure.GetTail()); }); }); Describe("when removing another structure", [this, Dir]() { BeforeEach([this, Dir]() { for (int32 Idx = 0; Idx < SetupBlocks.Num(); Idx += 2) { OtherStructure.Add(SetupBlocks[Idx], SetupBlocks[Idx + 1], Dir); } }); It("should result in empty structure.", [this, Dir]() { BlockStructure.Remove(OtherStructure, Dir); TEST_NULL(BlockStructure.GetHead()); TEST_NULL(BlockStructure.GetTail()); }); }); }); Describe("when some blocks already exist", [this, Dir]() { BeforeEach([this, Dir]() { for (int32 Idx = 0; Idx < SetupBlocks.Num(); Idx += 2) { BlockStructure.Add(SetupBlocks[Idx], SetupBlocks[Idx + 1], Dir); } }); It("should ignore zero size block on head.", [this, Dir]() { BlockStructure.Remove(11, 0, Dir); TEST_EQUAL(ToArrayU64(BlockStructure), SetupBlocks); }); It("should ignore zero size block on tail.", [this, Dir]() { BlockStructure.Remove(41, 0, Dir); TEST_EQUAL(ToArrayU64(BlockStructure), SetupBlocks); }); It("should ignore zero size block on middle block.", [this, Dir]() { BlockStructure.Remove(21, 0, Dir); TEST_EQUAL(ToArrayU64(BlockStructure), SetupBlocks); }); It("should ignore block before head.", [this, Dir]() { BlockStructure.Remove(5, 5, Dir); TEST_EQUAL(ToArrayU64(BlockStructure), SetupBlocks); }); It("should ignore block after tail.", [this, Dir]() { BlockStructure.Remove(45, 5, Dir); TEST_EQUAL(ToArrayU64(BlockStructure), SetupBlocks); }); It("should ignore block inside gap.", [this, Dir]() { BlockStructure.Remove(27, 3, Dir); TEST_EQUAL(ToArrayU64(BlockStructure), SetupBlocks); }); It("should remove head when exact match.", [this, Dir]() { BlockStructure.Remove(10, 5, Dir); TEST_EQUAL(ToArrayU64(BlockStructure), ArrayChopLeft(SetupBlocks, 2)); }); It("should remove head when overlapping.", [this, Dir]() { BlockStructure.Remove(9, 7, Dir); TEST_EQUAL(ToArrayU64(BlockStructure), ArrayChopLeft(SetupBlocks, 2)); }); It("should shrink start of head.", [this, Dir]() { BlockStructure.Remove(10, 2, Dir); TEST_EQUAL(ToArrayU64(BlockStructure), ArrayClobber(SetupBlocks, 0, ARRAYU64(12, 3))); }); It("should shrink end of head.", [this, Dir]() { BlockStructure.Remove(13, 2, Dir); TEST_EQUAL(ToArrayU64(BlockStructure), ArrayClobber(SetupBlocks, 0, ARRAYU64(10, 3))); }); It("should split head.", [this, Dir]() { BlockStructure.Remove(12, 1, Dir); TEST_EQUAL(ToArrayU64(BlockStructure), ARRAYU64(10, 2) + ArrayClobber(SetupBlocks, 0, ARRAYU64(13, 2))); }); It("should remove tail when exact match.", [this, Dir]() { BlockStructure.Remove(40, 5, Dir); TEST_EQUAL(ToArrayU64(BlockStructure), ArrayChopRight(SetupBlocks, 2)); }); It("should remove tail when overlapping.", [this, Dir]() { BlockStructure.Remove(39, 7, Dir); TEST_EQUAL(ToArrayU64(BlockStructure), ArrayChopRight(SetupBlocks, 2)); }); It("should shrink start of tail.", [this, Dir]() { BlockStructure.Remove(40, 2, Dir); TEST_EQUAL(ToArrayU64(BlockStructure), ArrayClobber(SetupBlocks, SetupBlocks.Num() - 2, ARRAYU64(42, 3))); }); It("should shrink end of tail.", [this, Dir]() { BlockStructure.Remove(43, 2, Dir); TEST_EQUAL(ToArrayU64(BlockStructure), ArrayClobber(SetupBlocks, SetupBlocks.Num() - 2, ARRAYU64(40, 3))); }); It("should split tail.", [this, Dir]() { BlockStructure.Remove(42, 1, Dir); TEST_EQUAL(ToArrayU64(BlockStructure), ArrayClobber(SetupBlocks, SetupBlocks.Num() - 2, ARRAYU64(40, 2)) + ARRAYU64(43, 2)); }); It("should remove block when exact match.", [this, Dir]() { BlockStructure.Remove(20, 7, Dir); TEST_EQUAL(ToArrayU64(BlockStructure), ArrayLeft(SetupBlocks, 2) + ArrayRight(SetupBlocks, 4)); }); It("should remove block when overlapping.", [this, Dir]() { BlockStructure.Remove(19, 9, Dir); TEST_EQUAL(ToArrayU64(BlockStructure), ArrayLeft(SetupBlocks, 2) + ArrayRight(SetupBlocks, 4)); }); It("should shrink start of block.", [this, Dir]() { BlockStructure.Remove(20, 2, Dir); TEST_EQUAL(ToArrayU64(BlockStructure), ArrayClobber(SetupBlocks, 2, ARRAYU64(22, 5))); }); It("should shrink end of block.", [this, Dir]() { BlockStructure.Remove(25, 2, Dir); TEST_EQUAL(ToArrayU64(BlockStructure), ArrayClobber(SetupBlocks, 2, ARRAYU64(20, 5))); }); It("should split block.", [this, Dir]() { BlockStructure.Remove(22, 3, Dir); TEST_EQUAL(ToArrayU64(BlockStructure), ArrayLeft(SetupBlocks, 2) + ARRAYU64(20, 2, 25, 2) + ArrayRight(SetupBlocks, 4)); }); It("should remove all blocks with exact overlap.", [this, Dir]() { BlockStructure.Remove(10, 35, Dir); TEST_NULL(BlockStructure.GetHead()); TEST_NULL(BlockStructure.GetTail()); }); It("should remove all blocks with extra overlap.", [this, Dir]() { BlockStructure.Remove(0, 100, Dir); TEST_NULL(BlockStructure.GetHead()); TEST_NULL(BlockStructure.GetTail()); }); It("should shrink semi overlapped head and tail, removing fully overlapped block.", [this, Dir]() { BlockStructure.Remove(12, 31, Dir); TEST_EQUAL(ToArrayU64(BlockStructure), ARRAYU64(10, 2, 43, 2)); }); It("should shrink semi overlapped blocks, removing fully overlapped block.", [this, Dir]() { BlockStructure.Remove(21, 11, Dir); TEST_EQUAL(ToArrayU64(BlockStructure), ArrayHead(SetupBlocks) + ARRAYU64(20, 1, 32, 1) + ArrayTail(SetupBlocks)); }); Describe("when removing another structure the same", [this, Dir]() { BeforeEach([this, Dir]() { for (int32 Idx = 0; Idx < SetupBlocks.Num(); Idx += 2) { OtherStructure.Add(SetupBlocks[Idx], SetupBlocks[Idx + 1], Dir); } }); It("should result in an empty structure.", [this, Dir]() { BlockStructure.Remove(OtherStructure, Dir); TEST_NULL(BlockStructure.GetHead()); TEST_NULL(BlockStructure.GetTail()); }); }); Describe("when removing a different structure with no overlap", [this, Dir]() { BeforeEach([this, Dir]() { OtherStructure.Add(5, 3, Dir); OtherStructure.Add(16, 3, Dir); OtherStructure.Add(28, 1, Dir); OtherStructure.Add(35, 4, Dir); OtherStructure.Add(50, 6, Dir); }); It("should result in the original structure.", [this, Dir]() { BlockStructure.Remove(OtherStructure, Dir); TEST_EQUAL(ToArrayU64(BlockStructure), SetupBlocks); }); }); Describe("when removing a different structure with some overlap", [this, Dir]() { BeforeEach([this, Dir]() { OtherStructure.Add(5, 7, Dir); OtherStructure.Add(19, 12, Dir); OtherStructure.Add(42, 2, Dir); }); It("should result in the chopped structure.", [this, Dir]() { BlockStructure.Remove(OtherStructure, Dir); TEST_EQUAL(ToArrayU64(BlockStructure), ARRAYU64(12, 3, 31, 2, 40, 2, 44, 1)); }); }); }); }); } }); Describe("SelectSerialBytes", [this]() { Describe("when the structure is empty", [this]() { It("should return 0.", [this]() { TEST_EQUAL(BlockStructure.SelectSerialBytes(0, 100, OtherStructure), 0); }); It("should not effect OutputStructure.", [this]() { BlockStructure.SelectSerialBytes(0, 100, OtherStructure); TEST_NULL(OtherStructure.GetHead()); TEST_NULL(OtherStructure.GetTail()); }); }); Describe("when the structure has enough bytes", [this]() { BeforeEach([this]() { for (int32 Idx = 0; Idx < SetupBlocks.Num(); Idx += 2) { BlockStructure.Add(SetupBlocks[Idx], SetupBlocks[Idx + 1]); } }); It("should supply exactly selected entire structure.", [this]() { TEST_EQUAL(BlockStructure.SelectSerialBytes(0, 20, OtherStructure), 20); TEST_EQUAL(ToArrayU64(OtherStructure), SetupBlocks); }); It("should supply exactly selected serial blocks.", [this]() { TEST_EQUAL(BlockStructure.SelectSerialBytes(5, 10, OtherStructure), 10); TEST_EQUAL(ToArrayU64(OtherStructure), ARRAYU64(20, 7, 30, 3)); }); It("should supply partially selected serial blocks, shrinking head and tail.", [this]() { TEST_EQUAL(BlockStructure.SelectSerialBytes(2, 15, OtherStructure), 15); TEST_EQUAL(ToArrayU64(OtherStructure), ARRAYU64(12, 3, 20, 7, 30, 3, 40, 2)); }); It("should supply partially selected serial blocks, shrinking two blocks.", [this]() { TEST_EQUAL(BlockStructure.SelectSerialBytes(7, 6, OtherStructure), 6); TEST_EQUAL(ToArrayU64(OtherStructure), ARRAYU64(22, 5, 30, 1)); }); It("should supply single portion of one block.", [this]() { TEST_EQUAL(BlockStructure.SelectSerialBytes(6, 5, OtherStructure), 5); TEST_EQUAL(ToArrayU64(OtherStructure), ARRAYU64(21, 5)); }); It("should supply partially selected serial blocks, skipping head by adjacent index.", [this]() { TEST_EQUAL(BlockStructure.SelectSerialBytes(5, 15, OtherStructure), 15); TEST_EQUAL(ToArrayU64(OtherStructure), ARRAYU64(20, 7, 30, 3, 40, 5)); }); It("should supply empty structure for selecting nothing, with intersecting index.", [this]() { TEST_EQUAL(BlockStructure.SelectSerialBytes(5, 0, OtherStructure), 0); TEST_NULL(OtherStructure.GetHead()); TEST_NULL(OtherStructure.GetTail()); }); It("should supply empty structure for selecting nothing, with blank index.", [this]() { TEST_EQUAL(BlockStructure.SelectSerialBytes(100, 0, OtherStructure), 0); TEST_NULL(OtherStructure.GetHead()); TEST_NULL(OtherStructure.GetTail()); }); }); Describe("when the structure has less bytes than requested", [this]() { BeforeEach([this]() { for (int32 Idx = 0; Idx < SetupBlocks.Num(); Idx += 2) { BlockStructure.Add(SetupBlocks[Idx], SetupBlocks[Idx + 1]); } }); It("should supply entire structure.", [this]() { TEST_EQUAL(BlockStructure.SelectSerialBytes(0, 1000, OtherStructure), 20); TEST_EQUAL(ToArrayU64(OtherStructure), SetupBlocks); }); It("should supply last part of structure, with intersecting index.", [this]() { TEST_EQUAL(BlockStructure.SelectSerialBytes(6, 1000, OtherStructure), 14); TEST_EQUAL(ToArrayU64(OtherStructure), ARRAYU64(21, 6, 30, 3, 40, 5)); }); }); }); Describe("Intersect", [this]() { BeforeEach([this]() { for (int32 Idx = 0; Idx < SetupBlocks.Num(); Idx += 2) { BlockStructure.Add(SetupBlocks[Idx], SetupBlocks[Idx + 1]); } }); Describe("when the given an empty structure", [this]() { It("should return an empty structure.", [this]() { FBlockStructure NewBlockStructure = BlockStructure.Intersect(OtherStructure); TEST_NULL(NewBlockStructure.GetHead()); TEST_NULL(NewBlockStructure.GetTail()); }); }); Describe("when given the same structure", [this]() { BeforeEach([this]() { for (int32 Idx = 0; Idx < SetupBlocks.Num(); Idx += 2) { OtherStructure.Add(SetupBlocks[Idx], SetupBlocks[Idx + 1]); } }); It("should return a structure of the same blocks.", [this]() { FBlockStructure NewBlockStructure = BlockStructure.Intersect(OtherStructure); TEST_EQUAL(ToArrayU64(NewBlockStructure), SetupBlocks); }); }); Describe("when given an inverted structure", [this]() { BeforeEach([this]() { OtherStructure.Add(0, 50); for (int32 Idx = 0; Idx < SetupBlocks.Num(); Idx += 2) { OtherStructure.Remove(SetupBlocks[Idx], SetupBlocks[Idx + 1]); } }); It("should return an empty structure.", [this]() { FBlockStructure NewBlockStructure = BlockStructure.Intersect(OtherStructure); TEST_NULL(NewBlockStructure.GetHead()); TEST_NULL(NewBlockStructure.GetTail()); }); }); Describe("when given an fully overlapping structure", [this]() { BeforeEach([this]() { OtherStructure.Add(0, 50); }); It("should return a structure of the same blocks.", [this]() { FBlockStructure NewBlockStructure = BlockStructure.Intersect(OtherStructure); TEST_EQUAL(ToArrayU64(NewBlockStructure), SetupBlocks); }); }); Describe("when given a structure with just the same head", [this]() { BeforeEach([this]() { OtherStructure.Add(ArrayHead(SetupBlocks)[0], ArrayHead(SetupBlocks)[1]); }); It("should return a structure with just the same head.", [this]() { FBlockStructure NewBlockStructure = BlockStructure.Intersect(OtherStructure); TEST_EQUAL(ToArrayU64(NewBlockStructure), ArrayHead(SetupBlocks)); }); }); Describe("when given a structure with just the same tail", [this]() { BeforeEach([this]() { OtherStructure.Add(ArrayTail(SetupBlocks)[0], ArrayTail(SetupBlocks)[1]); }); It("should return a structure with just the same tail.", [this]() { FBlockStructure NewBlockStructure = BlockStructure.Intersect(OtherStructure); TEST_EQUAL(ToArrayU64(NewBlockStructure), ArrayTail(SetupBlocks)); }); }); Describe("when given a structure with a single matching block", [this]() { BeforeEach([this]() { OtherStructure.Add(30, 3); }); It("should return a structure with the single matching block.", [this]() { FBlockStructure NewBlockStructure = BlockStructure.Intersect(OtherStructure); TEST_EQUAL(ToArrayU64(NewBlockStructure), ARRAYU64(30, 3)); }); }); Describe("when given a structure with every block slightly shrunk", [this]() { BeforeEach([this]() { for (int32 Idx = 0; Idx < SetupBlocks.Num(); Idx += 2) { OtherStructure.Add(SetupBlocks[Idx] + 1, SetupBlocks[Idx + 1] - 2); } }); It("should return a structure with every block slightly shrunk.", [this]() { FBlockStructure NewBlockStructure = BlockStructure.Intersect(OtherStructure); TEST_EQUAL(ToArrayU64(NewBlockStructure), ARRAYU64(11, 3, 21, 5, 31, 1, 41, 3)); }); }); Describe("when given a structure with every block slightly grown", [this]() { BeforeEach([this]() { for (int32 Idx = 0; Idx < SetupBlocks.Num(); Idx += 2) { OtherStructure.Add(SetupBlocks[Idx] - 1, SetupBlocks[Idx + 1] + 2); } }); It("should return a structure with every original block.", [this]() { FBlockStructure NewBlockStructure = BlockStructure.Intersect(OtherStructure); TEST_EQUAL(ToArrayU64(NewBlockStructure), SetupBlocks); }); }); Describe("when given a structure with start and end overlaps for all blocks", [this]() { BeforeEach([this]() { OtherStructure.Add(0, 50); for (int32 Idx = 0; Idx < SetupBlocks.Num(); Idx += 2) { OtherStructure.Remove(SetupBlocks[Idx] + 1, SetupBlocks[Idx + 1] - 2); } }); It("should return a structure with just the start and end bytes.", [this]() { FBlockStructure NewBlockStructure = BlockStructure.Intersect(OtherStructure); TEST_EQUAL(ToArrayU64(NewBlockStructure), ARRAYU64(10, 1, 14, 1, 20, 1, 26, 1, 30, 1, 32, 1, 40, 1, 44, 1)); }); }); }); Describe("ToString", [this]() { Describe("when the structure is empty", [this]() { It("should return empty string.", [this]() { TEST_EQUAL(BlockStructure.ToString(), TEXT("")); }); }); Describe("when the structure has blocks", [this]() { BeforeEach([this]() { for (int32 Idx = 0; Idx < SetupBlocks.Num(); Idx += 2) { BlockStructure.Add(SetupBlocks[Idx], SetupBlocks[Idx + 1]); } }); It("should return full string representation.", [this]() { TEST_EQUAL(BlockStructure.ToString(), TEXT("[10,5]-[20,7]-[30,3]-[40,5].")); }); It("should return clamped string representation.", [this]() { TEST_EQUAL(BlockStructure.ToString(2), TEXT("[10,5]-[20,7].. 2 more.")); }); }); }); }); } bool FBlockStructureSpec::IsLooping(const BuildPatchServices::FBlockStructure& Structure) { const BuildPatchServices::FBlockEntry* Slow = Structure.GetHead(); const BuildPatchServices::FBlockEntry* Fast = Structure.GetHead(); while (Slow != nullptr && Fast != nullptr && Fast->GetNext() != nullptr) { Slow = Slow->GetNext(); Fast = Fast->GetNext()->GetNext(); bool bIsLooping = Slow == Fast; TEST_FALSE(bIsLooping); if (bIsLooping) { return true; } } Slow = Structure.GetTail(); Fast = Structure.GetTail(); while (Slow != nullptr && Fast != nullptr && Fast->GetPrevious() != nullptr) { Slow = Slow->GetPrevious(); Fast = Fast->GetPrevious()->GetPrevious(); bool bIsLooping = Slow == Fast; TEST_FALSE(bIsLooping); if (bIsLooping) { return true; } } return false; } bool FBlockStructureSpec::IsMirrored(const BuildPatchServices::FBlockStructure& Structure) { bool bMirrored = false; if (!IsLooping(Structure)) { TArray Forward; const BuildPatchServices::FBlockEntry* Block = Structure.GetHead(); while (Block != nullptr) { Forward.Add(Block); Block = Block->GetNext(); } TArray Backward; Block = Structure.GetTail(); while (Block != nullptr) { Backward.Add(Block); Block = Block->GetPrevious(); } Algo::Reverse(Backward); bMirrored = Forward == Backward; } TEST_TRUE(bMirrored); return bMirrored; } TArray FBlockStructureSpec::ToArrayU64(const BuildPatchServices::FBlockStructure& Structure) { TArray Result; if (!IsLooping(Structure) && IsMirrored(Structure)) { const BuildPatchServices::FBlockEntry* Block = Structure.GetHead(); while (Block != nullptr) { Result.Add(Block->GetOffset()); Result.Add(Block->GetSize()); Block = Block->GetNext(); } } return Result; } TSet FBlockStructureSpec::EnumeratePtrs(const BuildPatchServices::FBlockStructure& Structure) { TSet Result; if (!IsLooping(Structure) && IsMirrored(Structure)) { const BuildPatchServices::FBlockEntry* Block = Structure.GetHead(); while (Block != nullptr) { Result.Add(Block); Block = Block->GetNext(); } } return Result; } #endif //WITH_DEV_AUTOMATION_TESTS