// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "UObject/Package.h" #if UE_ENABLE_INCLUDE_ORDER_DEPRECATED_IN_5_6 #include "UObject/UObjectGlobals.h" #include "CoreGlobals.h" #endif // UE_ENABLE_INCLUDE_ORDER_DEPRECATED_IN_5_6 #include "Misc/AutomationTest.h" #include "TestLogger.h" #include "Engine/EngineBaseTypes.h" #include "TestableEnsures.h" DECLARE_LOG_CATEGORY_EXTERN(LogAITestSuite, Log, All); DECLARE_LOG_CATEGORY_EXTERN(LogBehaviorTreeTest, Log, All); DEFINE_LATENT_AUTOMATION_COMMAND_ONE_PARAMETER(FAITestCommand_WaitSeconds, float, Duration); namespace UE::AITestSuite { /** Will cause UE_DEBUG_BREAK if `test.BreakOnTestFail` console variable is true */ AITESTSUITE_API void ConditionallyBreakOnTestFail(); } class FAITestCommand_WaitOneTick : public IAutomationLatentCommand { public: FAITestCommand_WaitOneTick() : bAlreadyRun(false) {} virtual bool Update() override; private: bool bAlreadyRun; }; namespace FAITestHelpers { AITESTSUITE_API UWorld* GetWorld(); static constexpr float TickInterval = 1.f / 30; void UpdateFrameCounter(); uint64 FramesCounter(); } struct FAITestBase { private: // internals TArray SpawnedObjects; uint32 bTearedDown : 1; protected: FAutomationTestBase* TestRunner; FAITestBase() : bTearedDown(false), TestRunner(nullptr) {} template ClassToSpawn* NewAutoDestroyObject(UObject* Outer = GetTransientPackage()) { ClassToSpawn* ObjectInstance = NewObject(Outer); ObjectInstance->AddToRoot(); SpawnedObjects.Add(ObjectInstance); return ObjectInstance; } AITESTSUITE_API void AddAutoDestroyObject(UObject& ObjectRef); AITESTSUITE_API virtual UWorld& GetWorld() const; FAutomationTestBase& GetTestRunner() const { check(TestRunner); return *TestRunner; } public: virtual void SetTestRunner(FAutomationTestBase& AutomationTestInstance) { TestRunner = &AutomationTestInstance; } // interface AITESTSUITE_API virtual ~FAITestBase(); /** @return true if setup was completed successfully, false otherwise (which will result in failing the test instance). */ virtual bool SetUp() { return true; } /** @return true to indicate that the test is done. */ virtual bool Update() { return false; } /** lets the Test instance test the results. Use AITEST_*_LATENT macros */ virtual void VerifyLatentResults() {} /** @return false to indicate an issue with test execution. Will signal to automation framework this test instance failed. */ virtual bool InstantTest() { return false;} // it's essential that overriding functions call the super-implementation. Otherwise, the check in ~FAITestBase will fail. AITESTSUITE_API virtual void TearDown(); }; DEFINE_EXPORTED_LATENT_AUTOMATION_COMMAND_ONE_PARAMETER(AITESTSUITE_API, FAITestCommand_SetUpTest, FAITestBase*, AITest); DEFINE_EXPORTED_LATENT_AUTOMATION_COMMAND_ONE_PARAMETER(AITESTSUITE_API, FAITestCommand_PerformTest, FAITestBase*, AITest); DEFINE_EXPORTED_LATENT_AUTOMATION_COMMAND_ONE_PARAMETER(AITESTSUITE_API, FAITestCommand_VerifyTestResults, FAITestBase*, AITest); DEFINE_EXPORTED_LATENT_AUTOMATION_COMMAND_ONE_PARAMETER(AITESTSUITE_API, FAITestCommand_TearDownTest, FAITestBase*, AITest); // @note that TestClass needs to derive from FAITestBase #define IMPLEMENT_AI_LATENT_TEST(TestClass, PrettyName) \ IMPLEMENT_SIMPLE_AUTOMATION_TEST(TestClass##_Runner, PrettyName, (EAutomationTestFlags::ClientContext | EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter)) \ bool TestClass##_Runner::RunTest(const FString& Parameters) \ { \ /* spawn test instance. Setup should be done in test's constructor */ \ TestClass* TestInstance = new TestClass(); \ TestInstance->SetTestRunner(*this); \ /* set up */ \ ADD_LATENT_AUTOMATION_COMMAND(FAITestCommand_SetUpTest(TestInstance)); \ /* run latent command to update */ \ ADD_LATENT_AUTOMATION_COMMAND(FAITestCommand_PerformTest(TestInstance)); \ /* let the Test instance verify the results, calls VerifyLatentResults */ \ ADD_LATENT_AUTOMATION_COMMAND(FAITestCommand_VerifyTestResults(TestInstance)); \ /* run latent command to tear down */ \ ADD_LATENT_AUTOMATION_COMMAND(FAITestCommand_TearDownTest(TestInstance)); \ return true; \ } #define IMPLEMENT_AI_INSTANT_TEST(TestClass, PrettyName) \ IMPLEMENT_SIMPLE_AUTOMATION_TEST(TestClass##Runner, PrettyName, (EAutomationTestFlags::ClientContext | EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter)) \ bool TestClass##Runner::RunTest(const FString& Parameters) \ { \ bool bSuccess = false; \ /* spawn test instance. */ \ TestClass* TestInstance = new TestClass(); \ TestInstance->SetTestRunner(*this); \ /* set up */ \ if (TestInstance->SetUp()) \ { \ /* call the instant-test code */ \ bSuccess = TestInstance->InstantTest(); \ }\ /* tear down */ \ TestInstance->TearDown(); \ delete TestInstance; \ return bSuccess; \ } /** * This macro allows one to implement a whole set of simple tests that share common setups. To use it first implement * a struct that builds the said common setup. Like so: * * struct FMyCommonSetup : public FAITestBase * { * virtual bool SetUp() override * { * // your test common setup build code here * * // return false if setup fails and the test needs to be aborted * return true; * } * }; * * Once that's done you can implement a specific test using this setup class like so: * * IMPLEMENT_INSTANT_TEST_WITH_FIXTURE(FMyCommonSetup, "System.Engine.AI.MyTestGroup", ThisSpecificTestName) * { * // your test code here * * // return false to indicate the whole test instance failed for some reason * return true; * } */ #define IMPLEMENT_INSTANT_TEST_WITH_FIXTURE(Fixture, PrettyGroupNameString, TestExperiment) \ struct Fixture##_##TestExperiment : public Fixture \ { \ virtual bool InstantTest() override; \ }; \ IMPLEMENT_AI_INSTANT_TEST(Fixture##_##TestExperiment, PrettyGroupNameString "." # TestExperiment) \ bool Fixture##_##TestExperiment::InstantTest() //----------------------------------------------------------------------// // Specific test types //----------------------------------------------------------------------// template struct FAITest_SimpleComponentBasedTest : public FAITestBase { FTestLogger Logger; FReal* Component; FAITest_SimpleComponentBasedTest() { Component = NewAutoDestroyObject(); } virtual void SetTestRunner(FAutomationTestBase& AutomationTestInstance) override { FAITestBase::SetTestRunner(AutomationTestInstance); Logger.TestRunner = TestRunner; } virtual ~FAITest_SimpleComponentBasedTest() override { GetTestRunner().TestTrue(TEXT("Not all expected values has been logged"), Logger.ExpectedValues.Num() == 0 || Logger.ExpectedValues.Num() == Logger.LoggedValues.Num()); } virtual bool SetUp() override { UWorld* World = FAITestHelpers::GetWorld(); Component->RegisterComponentWithWorld(World); return World != nullptr; } void TickComponent() { Component->TickComponent(FAITestHelpers::TickInterval, ELevelTick::LEVELTICK_All, nullptr); } }; //----------------------------------------------------------------------// // state testing macros, valid in FTestAIBase (and subclasses') methods // Using these macros makes sure the test function fails if the assertion // fails making sure the rest of the test relying on given condition being // true doesn't crash //----------------------------------------------------------------------// #define __AITEST_IMPL(What, Value, Test, RetVal)\ if (!GetTestRunner().Test(What, Value))\ {\ UE::AITestSuite::ConditionallyBreakOnTestFail(); \ return RetVal;\ } #define AITEST_TRUE(What, Value) __AITEST_IMPL(What, Value, TestTrue, false) #define AITEST_FALSE(What, Value) __AITEST_IMPL(What, Value, TestFalse, false) #define AITEST_NULL(What, Pointer) __AITEST_IMPL(What, Pointer, TestNull, false) #define AITEST_NOT_NULL(What, Pointer) \ __AITEST_IMPL(What, Pointer, TestNotNull, false); \ CA_ASSUME(Pointer) namespace FTestHelpers { template inline bool TestEqual(const FString& Description, T1 Expression, T2 Expected, FAutomationTestBase& This) { return This.TestEqual(*Description, Expression, Expected); } template inline bool TestEqual(const FString& Description, T1* Expression, T2* Expected, FAutomationTestBase& This) { return This.TestEqual(*Description, reinterpret_cast(Expression), reinterpret_cast(Expected)); } template inline bool TestNotEqual(const FString& Description, T1 Expression, T2 Expected, FAutomationTestBase& This) { return This.TestNotEqual(*Description, Expression, Expected); } template inline bool TestNotEqual(const FString& Description, T1* Expression, T2* Expected, FAutomationTestBase& This) { return This.TestNotEqual(*Description, reinterpret_cast(Expression), reinterpret_cast(Expected)); } inline void AddInfo(const FString& Info, FAutomationTestBase& This) { This.AddInfo(*Info); } } #define __AITEST_HELPER_IMPL(What, Actual, Expected, Test, RetVal)\ if (!FTestHelpers::Test(What, Actual, Expected, GetTestRunner()))\ {\ UE::AITestSuite::ConditionallyBreakOnTestFail(); \ return RetVal;\ } #define AITEST_EQUAL(What, Actual, Expected) __AITEST_HELPER_IMPL(What, Actual, Expected, TestEqual, false) #define AITEST_NOT_EQUAL(What, Actual, Expected) __AITEST_HELPER_IMPL(What, Actual, Expected, TestNotEqual, false) #define AITEST_INFO(InfoString) FTestHelpers::AddInfo(InfoString, GetTestRunner()) //----------------------------------------------------------------------// // state testing macros, valid in FTestAIBase (and subclasses') methods. // Using these macros makes sure the test function fails if the assertion // fails making sure the rest of the test relying on given condition being // true doesn't crash. Note that these macros are intended to be put in // a void-returning function where latent test's results can be verified. // Update function is not a good place for testing results since its return // value controls whether the test will continue. //----------------------------------------------------------------------// #define AITEST_TRUE_LATENT(What, Value) __AITEST_IMPL(What, Value, TestTrue, ) #define AITEST_FALSE_LATENT(What, Value) __AITEST_IMPL(What, Value, TestFalse, ) #define AITEST_NULL_LATENT(What, Pointer) __AITEST_IMPL(What, Pointer, TestNull, ) #define AITEST_NOT_NULL_LATENT(What, Pointer) __AITEST_IMPL(What, Pointer, TestNotNull, ) #define AITEST_EQUAL_LATENT(What, Actual, Expected) __AITEST_HELPER_IMPL(What, Actual, Expected, TestEqual, ) #define AITEST_NOT_EQUAL_LATENT(What, Actual, Expected) __AITEST_HELPER_IMPL(What, Actual, Expected, TestNotEqual, )