// Copyright Epic Games, Inc. All Rights Reserved. namespace { DEFINE_LOG_CATEGORY_STATIC(LogCqTest, Log, All); template struct TBeforeTestCommand : public IAutomationLatentCommand { TBeforeTestCommand(TBaseTest& InCurrentTest, TSharedRef InSequence) : CurrentTest(InCurrentTest) , Sequence(InSequence) {} bool Update() override { UE_LOG(LogCqTest, Log, TEXT("Before Test")); if (GEngine != nullptr) { //Do not collect garbage during the test. We force GC at the end. GEngine->DelayGarbageCollection(); } CurrentTest.Setup(); if (auto LatentActions = CurrentTest.TestCommandBuilder.Build()) { Sequence->Prepend(LatentActions); } return true; } TBaseTest& CurrentTest; TSharedRef Sequence; }; template struct TRunTestCommand : public IAutomationLatentCommand { TRunTestCommand(TBaseTest& InCurrentTest, const FString& InRequestedTest, const TTestRunner& InTestRunner, TSharedRef InSequence) : CurrentTest(InCurrentTest) , RequestedTest(InRequestedTest) , TestRunner(InTestRunner) , Sequence(InSequence) { } bool Update() override { UE_LOG(LogCqTest, Log, TEXT("RunTest")); if (TestRunner.HasAnyErrors()) { UE_LOG(LogCqTest, Log, TEXT("Skipping Test due to existing errors")); return true; // skip run if errors in BeforeTest } CurrentTest.RunTest(RequestedTest); if (auto LatentActions = CurrentTest.TestCommandBuilder.Build()) { Sequence->Prepend(LatentActions); } return true; } TBaseTest& CurrentTest; const FString& RequestedTest; const TTestRunner& TestRunner; TSharedRef Sequence; }; template struct TAfterTestCommand : public IAutomationLatentCommand { TAfterTestCommand(TBaseTest& InCurrentTest, TSharedRef InSequence) : CurrentTest(InCurrentTest) , Sequence(InSequence) {} bool Update() override { UE_LOG(LogCqTest, Log, TEXT("Running After Test")); CurrentTest.TearDown(); if (auto LatentActions = CurrentTest.TestCommandBuilder.BuildTearDown()) { Sequence->Prepend(LatentActions); } if (auto LatentActions = CurrentTest.TestCommandBuilder.Build()) { Sequence->Prepend(LatentActions); } return true; } TBaseTest& CurrentTest; TSharedRef Sequence; }; template struct TTearDownRunner : public IAutomationLatentCommand { explicit TTearDownRunner(TTestRunner& TestRunner) : TestRunner(TestRunner) {} bool Update() override { UE_LOG(LogCqTest, Log, TEXT("Tearing Down Test")); TestRunner.SetSuppressLogWarnings(ECQTestSuppressLogBehavior::Default); TestRunner.SetSuppressLogErrors(ECQTestSuppressLogBehavior::Default); TestRunner.CurrentTestPtr = nullptr; if (GEngine != nullptr) { //Force GC at the end of every test. GEngine->ForceGarbageCollection(); } return true; } TTestRunner& TestRunner; }; } // namespace template inline TTestRunner::TTestRunner(FString InName, int32 InLineNumber, const char* InFileName, FString InTestDir, EAutomationTestFlags InTestFlags, TTestInstanceGenerator InFactory, FString InTestTags) : FAutomationTestBase(InName, true) , LineNumber(InLineNumber) , FileName(FString(InFileName)) , TestDir(InTestDir) , TestFlags(InTestFlags) , TestInstanceFactory(InFactory) , TestTags(InTestTags) { bInitializing = true; if (TestDir.Equals(GenerateTestDirectory)) { TestDir = TestDirectoryGenerator::Generate(FileName); } else if (TestDir.Contains(TEXT("[GenerateTestDirectory]"), ESearchCase::IgnoreCase)) { TestDir = TestDir.Replace(TEXT("[GenerateTestDirectory]"), *TestDirectoryGenerator::Generate(FileName), ESearchCase::IgnoreCase); } CurrentTestPtr = TestInstanceFactory(*this); if (CurrentTestPtr->BeforeAllFunc) { BeforeAllDelegate = FAutomationTestFramework::Get().GetOnEnteringTestSection(GetBeautifiedTestName()).AddStatic(CurrentTestPtr->BeforeAllFunc); } if (CurrentTestPtr->AfterAllFunc) { AfterAllDelegate = FAutomationTestFramework::Get().GetOnLeavingTestSection(GetBeautifiedTestName()).AddStatic(CurrentTestPtr->AfterAllFunc); } bInitializing = false; } template inline TTestRunner::~TTestRunner() { if (BeforeAllDelegate.IsValid()) { FAutomationTestFramework::Get().GetOnEnteringTestSection(GetBeautifiedTestName()).Remove(BeforeAllDelegate); } if (AfterAllDelegate.IsValid()) { FAutomationTestFramework::Get().GetOnLeavingTestSection(GetBeautifiedTestName()).Remove(AfterAllDelegate); } } template inline bool TTestRunner::RunTest(const FString& RequestedTest) { if (RequestedTest.Len() == 0) { return false; } CurrentTestPtr = TestInstanceFactory(*this); check(CurrentTestPtr != nullptr); auto& CurrentTest = *CurrentTestPtr; TSharedRef CommandSequence = MakeShareable(new FRunSequence()); auto Before = MakeShared>(CurrentTest, CommandSequence); auto Run = MakeShared>(CurrentTest, RequestedTest, *this, CommandSequence); auto After = MakeShared>(CurrentTest, CommandSequence); auto TearDown = MakeShared>(*this); auto RemainingSteps = TArray>{ Before, Run, After, TearDown }; while (!RemainingSteps.IsEmpty()) { RemainingSteps[0]->Update(); RemainingSteps.RemoveAt(0); if (CurrentTestPtr != nullptr && !CommandSequence->IsEmpty()) { CommandSequence->AppendAll(RemainingSteps); FAutomationTestFramework::Get().EnqueueLatentCommand(CommandSequence); return true; } } return true; } template inline FString TTestRunner::GetBeautifiedTestName() const { return FString::Printf(TEXT("%s.%s"), *TestDir, *TestName); } template inline uint32 TTestRunner::GetRequiredDeviceNum() const { return 1; } template inline EAutomationTestFlags TTestRunner::GetTestFlags() const { return TestFlags; } template inline FString TTestRunner::GetTestSourceFileName() const { return FileName; } template inline int32 TTestRunner::GetTestSourceFileLine() const { return LineNumber; } template inline int32 TTestRunner::GetTestSourceFileLine(const FString& Name) const { FString TestParam(Name); int32 Pos = Name.Find(TEXT(" ")); if (Pos != INDEX_NONE) { TestParam = Name.RightChop(Pos + 1); } if (TestNames.Contains(TestParam)) { return TestLineNumbers[TestParam]; } return GetTestSourceFileLine(); } template inline bool TTestRunner::SuppressLogWarnings() { if (SuppressLogWarningsBehavior == ECQTestSuppressLogBehavior::Default) { return bSuppressLogWarnings; } return SuppressLogWarningsBehavior == ECQTestSuppressLogBehavior::True; } template inline bool TTestRunner::SuppressLogErrors() { if (SuppressLogErrorsBehavior == ECQTestSuppressLogBehavior::Default) { return bSuppressLogErrors; } return SuppressLogErrorsBehavior == ECQTestSuppressLogBehavior::True; } template inline void TTestRunner::SetSuppressLogWarnings(ECQTestSuppressLogBehavior Behavior) { SuppressLogWarningsBehavior = Behavior; } template inline void TTestRunner::SetSuppressLogErrors(ECQTestSuppressLogBehavior Behavior) { SuppressLogErrorsBehavior = Behavior; } template inline void TTestRunner::GetTests(TArray& OutBeautifiedNames, TArray& OutTestCommands) const { for (const auto& testName : TestNames) { OutBeautifiedNames.Add(testName); OutTestCommands.Add(testName); } } ///////////// template inline void TTest::RunTest(const FString& TestName) { auto TestMethod = Methods[TestName]; Derived& Self = static_cast(*this); (Self.*(TestMethod))(); } template inline TBaseTest::TBaseTest(FAutomationTestBase& TestRunner, bool bInitializing) : bInitializing(bInitializing) , TestRunner(TestRunner) , Assert(AsserterType{ TestRunner }) , TestCommandBuilder(FTestCommandBuilder{ TestRunner }) { } template inline void TBaseTest::AddCommand(IAutomationLatentCommand* Cmd) { TestCommandBuilder.CommandQueue.Add(MakeShareable(Cmd)); } template inline void TBaseTest::AddCommand(TSharedPtr Cmd) { TestCommandBuilder.CommandQueue.Add(Cmd); } template inline void TBaseTest::AddError(const FString& InError) const { TestRunner.AddError(InError, 0); } template inline bool TBaseTest::AddErrorIfFalse(bool bCondition, const FString& InError) const { return TestRunner.AddErrorIfFalse(bCondition, InError, 0); } template inline void TBaseTest::AddWarning(const FString& InWarning) const { TestRunner.AddWarning(InWarning, 0); } template inline void TBaseTest::AddInfo(const FString& InLogItem) const { TestRunner.AddInfo(InLogItem, 0, false); }