// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "Algo/Reverse.h" #include "Misc/AutomationTest.h" #include "Commands/TestCommands.h" class FTestCommandBuilder { public: FTestCommandBuilder(FAutomationTestBase& InTestRunner) : TestRunner(InTestRunner) {} ~FTestCommandBuilder() { checkf(CommandQueue.IsEmpty(), TEXT("Adding latent actions from within latent actions is currently unsupported.")); } FTestCommandBuilder& Do(const TCHAR* Description, TFunction Action) { if (!TestRunner.HasAnyErrors()) { CommandQueue.Add(MakeShared(TestRunner, Action, Description)); } return *this; } FTestCommandBuilder& Do(TFunction Action) { return Do(nullptr, Action); } FTestCommandBuilder& Then(TFunction Action) { return Do(Action); } FTestCommandBuilder& Then(const TCHAR* Description, TFunction Action) { return Do(Description, Action); } FTestCommandBuilder& Until(const TCHAR* Description, TFunction Query, TOptional Timeout = CQTest::DefaultTimeout) { if (!TestRunner.HasAnyErrors()) { CommandQueue.Add(MakeShared(TestRunner, Query, Timeout, Description)); } return *this; } FTestCommandBuilder& Until(TFunction Query, TOptional Timeout = CQTest::DefaultTimeout) { return Until(nullptr, Query, Timeout); } template FTestCommandBuilder& DoAsync(const TCHAR* Description, TFunction()> AsyncAction, TOptional Timeout = CQTest::DefaultTimeout) { if (!TestRunner.HasAnyErrors()) { CommandQueue.Add(MakeShared>(TestRunner, MoveTemp(AsyncAction), Timeout, Description)); } return *this; } template FTestCommandBuilder& DoAsync(TFunction()> AsyncAction, TOptional Timeout = CQTest::DefaultTimeout) { return DoAsync(nullptr, MoveTemp(AsyncAction), Timeout); } template typename TEnableIf, FTestCommandBuilder&>::Type DoAsync( const TCHAR* Description, TFunction()> AsyncAction, TFunction::Type)> ResultCallback, TOptional Timeout = CQTest::DefaultTimeout) { if (!TestRunner.HasAnyErrors()) { CommandQueue.Add(MakeShared>(TestRunner, MoveTemp(AsyncAction), Timeout, Description, MoveTemp(ResultCallback))); } return *this; } template typename TEnableIf, FTestCommandBuilder&>::Type DoAsync( TFunction()> AsyncAction, TFunction::Type)> ResultCallback, TOptional Timeout = CQTest::DefaultTimeout) { return DoAsync(nullptr, MoveTemp(AsyncAction), MoveTemp(ResultCallback), Timeout); } template FTestCommandBuilder& ThenAsync(const TCHAR* Description, TFunction()> AsyncAction, TOptional Timeout = CQTest::DefaultTimeout) { return DoAsync(Description, MoveTemp(AsyncAction), Timeout); } template FTestCommandBuilder& ThenAsync(TFunction()> AsyncAction, TOptional Timeout = CQTest::DefaultTimeout) { return DoAsync(nullptr, MoveTemp(AsyncAction), Timeout); } template typename TEnableIf, FTestCommandBuilder&>::Type ThenAsync( const TCHAR* Description, TFunction()> AsyncAction, TFunction::Type)> ResultCallback, TOptional Timeout = CQTest::DefaultTimeout) { return DoAsync(Description, MoveTemp(AsyncAction), MoveTemp(ResultCallback), Timeout); } template typename TEnableIf, FTestCommandBuilder&>::Type ThenAsync( TFunction()> AsyncAction, TFunction::Type)> ResultCallback, TOptional Timeout = CQTest::DefaultTimeout) { return DoAsync(nullptr, MoveTemp(AsyncAction), MoveTemp(ResultCallback), Timeout); } template typename TEnableIf, FTestCommandBuilder&>::Type UntilAsync( const TCHAR* Description, TFunction()> AsyncAction, TFunction::Type)> ResultCallback, TOptional AsyncActionTimeout = CQTest::DefaultTimeout, TOptional ConditionTimeout = CQTest::DefaultTimeout) { if (!TestRunner.HasAnyErrors()) { CommandQueue.Add(MakeShared>(TestRunner, MoveTemp(AsyncAction), AsyncActionTimeout, Description, MoveTemp(ResultCallback), ConditionTimeout)); } return *this; } template typename TEnableIf, FTestCommandBuilder&>::Type UntilAsync( TFunction()> AsyncAction, TFunction::Type)> ResultCallback, TOptional AsyncActionTimeout = CQTest::DefaultTimeout, TOptional ConditionTimeout = CQTest::DefaultTimeout) { return UntilAsync(nullptr, MoveTemp(AsyncAction), MoveTemp(ResultCallback), AsyncActionTimeout, ConditionTimeout); } FTestCommandBuilder& StartWhen(TFunction Query, TOptional Timeout = CQTest::DefaultTimeout) { return Until(Query, Timeout); } FTestCommandBuilder& StartWhen(const TCHAR* Description, TFunction Query, TOptional Timeout = CQTest::DefaultTimeout) { return Until(Description, Query, Timeout); } /** Note that using a timed-wait can introduce test flakiness due to variable runtimes. Please consider using `Until` and waiting until something happens instead. */ FTestCommandBuilder& WaitDelay(FTimespan Timeout) { return WaitDelay(nullptr, Timeout); } /** Note that using a timed-wait can introduce test flakiness due to variable runtimes. Please consider using `Until` and waiting until something happens instead. */ FTestCommandBuilder& WaitDelay(const TCHAR* Description, FTimespan Timeout) { if (!TestRunner.HasAnyErrors()) { CommandQueue.Add(MakeShared(TestRunner, Timeout, Description)); } return *this; } FTestCommandBuilder& OnTearDown(const TCHAR* Description, TFunction Action) { if (!TestRunner.HasAnyErrors()) { TearDownQueue.Add(MakeShared(TestRunner, Action, Description, ECQTestFailureBehavior::Run)); } return *this; } FTestCommandBuilder& OnTearDown(TFunction Action) { return OnTearDown(nullptr, Action); } FTestCommandBuilder& CleanUpWith(const TCHAR* Description, TFunction Action) { return OnTearDown(Description, Action); } FTestCommandBuilder& CleanUpWith(TFunction Action) { return OnTearDown(nullptr, Action); } TSharedPtr Build() { return BuildQueue(CommandQueue); } TSharedPtr BuildTearDown() { // Last in, first out Algo::Reverse(TearDownQueue); return BuildQueue(TearDownQueue); } private: TSharedPtr BuildQueue(TArray>& Queue) { TSharedPtr Result = nullptr; if (Queue.Num() == 0) { return Result; } else if (Queue.Num() == 1) { Result = Queue[0]; } else { Result = MakeShared(Queue); } Queue.Empty(); return Result; } protected: TArray> CommandQueue{}; TArray> TearDownQueue{}; FAutomationTestBase& TestRunner; template friend struct TBaseTest; };