// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "Misc/AutomationTest.h" #include "Async/AsyncResult.h" #define UE_API CQTEST_API namespace CQTest { inline static const TOptional DefaultTimeout = NullOpt; } /** Latent Command that waits until the Query evaluates to `true` or the timeout has exceeded. */ class FWaitUntil : public IAutomationLatentCommand { public: FWaitUntil(FAutomationTestBase& InTestRunner, TFunction Query, TOptional Timeout = CQTest::DefaultTimeout, const TCHAR* InDescription = nullptr) : TestRunner(InTestRunner) , Query(MoveTemp(Query)) , Timeout(MakeTimeout(Timeout)) , Description(InDescription) {} UE_API bool Update() override; FAutomationTestBase& TestRunner; TFunction Query; FTimespan Timeout; FDateTime StartTime; const TCHAR* Description; bool bHasTimerStarted = false; private: UE_API FTimespan MakeTimeout(TOptional InTimeout); }; /** Latent Command that waits a set time frame. * Note that using a timed-wait can introduce test flakiness due to variable runtimes. Please consider using `FWaitUntil` and waiting until something happens instead. */ class FWaitDelay : public IAutomationLatentCommand { public: FWaitDelay(FAutomationTestBase& InTestRunner, FTimespan Timeout, const TCHAR* InDescription = nullptr) : TestRunner(InTestRunner) , Timeout(Timeout) , Description(InDescription) {} UE_API bool Update() override; FAutomationTestBase& TestRunner; FTimespan Timeout; FDateTime EndTime; const TCHAR* Description; bool bHasTimerStarted = false; }; enum class ECQTestFailureBehavior { Skip, Run }; /** Latent Command which executes the provided function. */ class FExecute : public IAutomationLatentCommand { public: FExecute(FAutomationTestBase& InTestRunner, TFunction Func, const TCHAR* InDescription = nullptr, ECQTestFailureBehavior InFailureBehavior = ECQTestFailureBehavior::Skip) : TestRunner(InTestRunner) , Func(MoveTemp(Func)) , Description(InDescription) , FailureBehavior(InFailureBehavior) {} UE_API bool Update() override; FAutomationTestBase& TestRunner; TFunction Func; const TCHAR* Description = nullptr; ECQTestFailureBehavior FailureBehavior; }; /** Latent Command which manages and executes an array of latent commands. */ class FRunSequence : public IAutomationLatentCommand { public: FRunSequence(const TArray>& ToAdd) : Commands(ToAdd) { } template FRunSequence(Cmds... Commands) : FRunSequence(TArray>{ Commands... }) { } UE_API void Append(TSharedPtr ToAdd); UE_API void AppendAll(TArray < TSharedPtr> ToAdd); UE_API void Prepend(TSharedPtr ToAdd); bool IsEmpty() const { return Commands.IsEmpty(); } UE_API bool Update() override; TArray> Commands; }; template struct TAsyncResultCallbackArg { using Type = decltype(std::declval>().GetFuture().Get()); }; template class TAsyncExecute; /** This namespace provides a set of variables and functions intended for internal use within the TAsyncExecute class */ namespace AsyncExecuteDetails { static const TCHAR* CreateInternalCommandDescription(TArray& OutDescriptions, const TCHAR* External, const TCHAR* Internal) { if (!External) { return nullptr; } OutDescriptions.Emplace(FString::Printf(TEXT("%s [%s]"), External, Internal)); return *OutDescriptions.Last(); } template class TResultCommandFactory; /** This class provides a helper function for creating a FExecute command to handle the result of an async operation. */ template class TResultCommandFactory { static TSharedRef Create( FAutomationTestBase& TestRunner, TAsyncResult& AsyncResult, TArray& OutDescriptions, const TCHAR* BaseDescription, TFunction::Type)>&& InFunc) { return MakeShared( TestRunner, [&Result = AsyncResult, Func = MoveTemp(InFunc)]() { return Func(Result.GetFuture().Get()); }, CreateInternalCommandDescription(OutDescriptions, BaseDescription, TEXT("Handle result [FExecute]")) ); } friend TAsyncExecute; }; /** This class provides a helper function for creating a FWaitUntil command to handle the result of an async operation. */ template class TResultCommandFactory { static TSharedRef Create( FAutomationTestBase& TestRunner, TAsyncResult& AsyncResult, TArray& OutDescriptions, const TCHAR* BaseDescription, TFunction::Type)>&& InFunc, TOptional Timeout = CQTest::DefaultTimeout) { return MakeShared( TestRunner, [&Result = AsyncResult, Func = MoveTemp(InFunc)]() { return Func(Result.GetFuture().Get()); }, Timeout, CreateInternalCommandDescription(OutDescriptions, BaseDescription, TEXT("Handle result [FWaitUntil]")) ); } friend TAsyncExecute; }; }; /** Latent Command that executes an async action and optionally processes the result in a Latent Command of the specified type. */ template class TAsyncExecute : public IAutomationLatentCommand { public: template TAsyncExecute( FAutomationTestBase& TestRunner, TFunction()> AsyncAction, TOptional AsyncActionTimeout = CQTest::DefaultTimeout, const TCHAR* Description = nullptr, ResultCommandArgs&&... Args ) { RunSequence.Append(MakeShared( TestRunner, [&Result = AsyncResult, Action = MoveTemp(AsyncAction)]() { Result = Action(); }, AsyncExecuteDetails::CreateInternalCommandDescription(Descriptions, Description, TEXT("Execute async action")) )); RunSequence.Append(MakeShared( TestRunner, [&Result = AsyncResult]() { return Result.GetFuture().IsReady(); }, AsyncActionTimeout, AsyncExecuteDetails::CreateInternalCommandDescription(Descriptions, Description, TEXT("Wait async result")) )); if constexpr (!std::is_void_v) { static_assert(!std::is_void_v, "The 'void' should not be used with . " "Instead, consider using a standalone command for any subsequent actions."); RunSequence.Append(AsyncExecuteDetails::TResultCommandFactory::Create( TestRunner, AsyncResult, Descriptions, Description, std::forward(Args)...) ); } }; bool Update() override { return RunSequence.Update(); } private: FRunSequence RunSequence; TAsyncResult AsyncResult; TArray Descriptions; }; #undef UE_API