// Copyright Epic Games, Inc. All Rights Reserved. #include "AutomationReport.h" #include "AutomationTestExcludelist.h" #include "Misc/FilterCollection.h" #include "HAL/FileManager.h" #include "Misc/FileHelper.h" #include "Misc/Paths.h" FAutomationReport::FAutomationReport(FAutomationTestInfo& InTestInfo, bool InIsParent) : bEnabled( false ) , bIsParent(InIsParent) , bNodeExpandInUI(false) , bSelfPassesFilter(false) , SupportFlags(0) , TestInfo( InTestInfo ) { // Enable smoke tests if ( TestInfo.GetTestFlags() == EAutomationTestFlags::SmokeFilter ) { bEnabled = true; } // Get exclude test info from Config/DefaultEngine.ini if (auto Entry = UAutomationTestExcludelist::Get()->GetExcludeTestEntry(TestInfo.GetFullTestPath())) { ExcludeTestInfo = *Entry; ExcludeTestInfo.SetPropagation(TestInfo.GetFullTestPath()); bNeedToSkip = true; } } void FAutomationReport::Empty() { //release references to all child tests ChildReports.Empty(); ChildReportNameHashes.Empty(); FilteredChildReports.Empty(); } FString FAutomationReport::GetTestParameter() const { return TestInfo.GetTestParameter(); } FString FAutomationReport::GetTags() const { return TestInfo.GetTestTags(); } FString FAutomationReport::GetAssetPath() const { return TestInfo.GetAssetPath(); } FString FAutomationReport::GetOpenCommand() const { return TestInfo.GetOpenCommand(); } FString FAutomationReport::GetCommand() const { return TestInfo.GetTestName(); } const FString& FAutomationReport::GetDisplayName() const { return TestInfo.GetDisplayName(); } const FString& FAutomationReport::GetFullTestPath() const { return TestInfo.GetFullTestPath(); } FString FAutomationReport::GetDisplayNameWithDecoration() const { FString FinalDisplayName = TestInfo.GetDisplayName(); //if this is an internal leaf node and the "decoration" name is being requested if (FilteredChildReports.Num()) { int32 NumChildren = GetTotalNumFilteredChildren(); //append on the number of child tests return TestInfo.GetDisplayName() + FString::Printf(TEXT(" (%d)"), NumChildren); } return FinalDisplayName; } int32 FAutomationReport::GetTotalNumChildren() const { int32 Total = 0; for (int32 ChildIndex = 0; ChildIndex < ChildReports.Num(); ++ChildIndex) { int ChildCount = ChildReports[ChildIndex]->GetTotalNumChildren(); //Only count leaf nodes if (ChildCount == 0) { Total ++; } Total += ChildCount; } return Total; } int32 FAutomationReport::GetTotalNumFilteredChildren() const { int32 Total = 0; for ( int32 ChildIndex = 0; ChildIndex < FilteredChildReports.Num(); ++ChildIndex ) { int ChildCount = FilteredChildReports[ChildIndex]->GetTotalNumFilteredChildren(); //Only count leaf nodes if ( ChildCount == 0 ) { Total++; } Total += ChildCount; } return Total; } void FAutomationReport::GetEnabledTestNames(TArray& OutEnabledTestNames, FString CurrentPath) const { LLM_SCOPE_BYNAME(TEXT("AutomationTest/Report")); //if this is a leaf and this test is enabled if ((ChildReports.Num() == 0) && IsEnabled()) { const FString FullTestName = CurrentPath.Len() > 0 ? CurrentPath.AppendChar(TCHAR('.')) + TestInfo.GetDisplayName() : TestInfo.GetDisplayName(); OutEnabledTestNames.Add(FullTestName); } else { if( !CurrentPath.IsEmpty() ) { CurrentPath += TEXT("."); } CurrentPath += TestInfo.GetDisplayName(); //recurse through the hierarchy for (int32 ChildIndex = 0; ChildIndex < ChildReports.Num(); ++ChildIndex) { ChildReports[ChildIndex]->GetEnabledTestNames(OutEnabledTestNames,CurrentPath); } } return; } void FAutomationReport::GetFilteredTestNames(TArray& OutFilteredTestNames, FString CurrentPath) const { LLM_SCOPE_BYNAME(TEXT("AutomationTest/Report")); // start from FilteredChildReports if (CurrentPath.IsEmpty()) { for (int32 FilteredChildIndex = 0; FilteredChildIndex < FilteredChildReports.Num(); ++FilteredChildIndex) { FilteredChildReports[FilteredChildIndex]->GetFilteredTestNames(OutFilteredTestNames, TestInfo.GetDisplayName()); } } else // then continue collecting all leaf nodes { //if this is a leaf collect full test name if (FilteredChildReports.Num() == 0) { const FString FullTestName = CurrentPath.Len() > 0 ? CurrentPath.AppendChar(TCHAR('.')) + TestInfo.GetDisplayName() : TestInfo.GetDisplayName(); OutFilteredTestNames.Add(FullTestName); } else { if (!CurrentPath.IsEmpty()) { CurrentPath += TEXT("."); } CurrentPath += TestInfo.GetDisplayName(); //recurse through the hierarchy for (int32 ChildIndex = 0; ChildIndex < FilteredChildReports.Num(); ++ChildIndex) { FilteredChildReports[ChildIndex]->GetFilteredTestNames(OutFilteredTestNames, CurrentPath); } } } return; } void FAutomationReport::SetEnabledTests(const TArray& InEnabledTests, FString CurrentPath) { if (ChildReports.Num() == 0) { //Find of the full name of this test and see if it is in our list const FString FullTestName = CurrentPath.Len() > 0 ? CurrentPath.AppendChar(TCHAR('.')) + TestInfo.GetDisplayName() : TestInfo.GetDisplayName(); const bool bNewEnabled = InEnabledTests.Contains(FullTestName); SetEnabled(bNewEnabled); } else { if( !CurrentPath.IsEmpty() ) { CurrentPath += TEXT("."); } CurrentPath += TestInfo.GetDisplayName(); //recurse through the hierarchy for (int32 ChildIndex = 0; ChildIndex < ChildReports.Num(); ++ChildIndex) { ChildReports[ChildIndex]->SetEnabledTests(InEnabledTests,CurrentPath); } //Parent nodes should be checked if all of its children are const int32 TotalNumChildern = GetTotalNumChildren(); const int32 EnabledChildren = GetEnabledTestsNum(); bEnabled = (TotalNumChildern == EnabledChildren); } } int32 FAutomationReport::GetEnabledTestsNum() const { int32 Total = 0; //if this is a leaf and this test is enabled if ((ChildReports.Num() == 0) && IsEnabled()) { Total++; } else { //recurse through the hierarchy for (int32 ChildIndex = 0; ChildIndex < ChildReports.Num(); ++ChildIndex) { Total += ChildReports[ChildIndex]->GetEnabledTestsNum(); } } return Total; } bool FAutomationReport::IsEnabled() const { return bEnabled; } void FAutomationReport::SetEnabled(bool bShouldBeEnabled) { bEnabled = bShouldBeEnabled; //set children to the same value for (int32 ChildIndex = 0; ChildIndex < FilteredChildReports.Num(); ++ChildIndex) { FilteredChildReports[ChildIndex]->SetEnabled(bShouldBeEnabled); } } void FAutomationReport::SetSupport(const int32 ClusterIndex) { SupportFlags |= (1< AutomationTestResult; AutomationTestResult.Add( FAutomationTestResults() ); Results.Add( AutomationTestResult ); } } bool FAutomationReport::IsSupported(const int32 ClusterIndex) const { return (SupportFlags & (1< InFilter, const bool ParentPassedFilter ) { LLM_SCOPE_BYNAME(TEXT("AutomationTest/Report")); //assume that this node and all its children fail to pass the filter test bool bSelfOrChildPassedFilter = false; //assume this node should not be expanded in the UI bNodeExpandInUI = false; //test for empty search string or matching search string bSelfPassesFilter = InFilter->PassesAllFilters( SharedThis( this ) ); if ( IsParent() && ParentPassedFilter ) { bSelfPassesFilter = true; } //clear the currently filtered tests array FilteredChildReports.Empty(); //see if any children pass the filter for( int32 ChildIndex = 0; ChildIndex < ChildReports.Num(); ++ChildIndex ) { bool ThisChildPassedFilter = ChildReports[ChildIndex]->SetFilter( InFilter, bSelfPassesFilter ); if( ThisChildPassedFilter ) { if ( !ChildReports[ChildIndex]->IsParent() || ChildReports[ChildIndex]->GetFilteredChildren().Num() > 0 ) { FilteredChildReports.Add(ChildReports[ChildIndex]); } } if ( bNodeExpandInUI == false && ThisChildPassedFilter == true ) { // A child node has passed the filter, so we want to expand this node in the UI bNodeExpandInUI = true; } } //if we passed name, speed, and status tests if( bSelfPassesFilter || bNodeExpandInUI ) { //Passed the filter! bSelfOrChildPassedFilter = true; } return bSelfOrChildPassedFilter; } TArray >& FAutomationReport::GetFilteredChildren() { return FilteredChildReports; } TArray >& FAutomationReport::GetChildReports() { return ChildReports; } void FAutomationReport::ClustersUpdated(const int32 NumClusters) { LLM_SCOPE_BYNAME(TEXT("AutomationTest/Report")); TestInfo.ResetNumDevicesRunningTest(); //Fixup Support flags SupportFlags = 0; for (int32 i = 0; i <= NumClusters; ++i) { SupportFlags |= (1< Results.Num() ) { for( int32 ClusterIndex = Results.Num(); ClusterIndex < NumClusters; ++ClusterIndex ) { //Make sure we have enough results for a single pass TArray AutomationTestResult; AutomationTestResult.Add( FAutomationTestResults() ); Results.Add( AutomationTestResult ); } } else if( NumClusters < Results.Num() ) { Results.RemoveAt(NumClusters, Results.Num() - NumClusters); } //recurse to children for (int32 ChildIndex = 0; ChildIndex < ChildReports.Num(); ++ChildIndex) { ChildReports[ChildIndex]->ClustersUpdated(NumClusters); } } void FAutomationReport::ResetForExecution(const int32 NumTestPasses) { TestInfo.ResetNumDevicesRunningTest(); //if this test is enabled if (IsEnabled()) { for (int32 ClusterIndex = 0; ClusterIndex < Results.Num(); ++ClusterIndex) { //Make sure we have enough results if( NumTestPasses > Results[ClusterIndex].Num() ) { for(int32 PassCount = Results[ClusterIndex].Num(); PassCount < NumTestPasses; ++PassCount) { Results[ClusterIndex].Add( FAutomationTestResults() ); } } else if( NumTestPasses < Results[ClusterIndex].Num() ) { Results[ClusterIndex].RemoveAt(NumTestPasses, Results[ClusterIndex].Num() - NumTestPasses); } for( int32 PassIndex = 0; PassIndex < Results[ClusterIndex].Num(); ++PassIndex) { //reset all stats Results[ClusterIndex][PassIndex].Reset(); } } } //recurse to children for (int32 ChildIndex = 0; ChildIndex < ChildReports.Num(); ++ChildIndex) { ChildReports[ChildIndex]->ResetForExecution(NumTestPasses); } } void FAutomationReport::SetResults( const int32 ClusterIndex, const int32 PassIndex, const FAutomationTestResults& InResults ) { //verify this is a platform this test is aware of check((ClusterIndex >= 0) && (ClusterIndex < Results.Num())); check((PassIndex >= 0) && (PassIndex < Results[ClusterIndex].Num())); if( InResults.State == EAutomationState::InProcess ) { TestInfo.InformOfNewDeviceRunningTest(); } LLM_SCOPE_BYNAME(TEXT("AutomationTest/Report")); const TArray ExistingArtifacts = Results[ClusterIndex][PassIndex].Artifacts; Results[ClusterIndex][PassIndex] = InResults; Results[ClusterIndex][PassIndex].Artifacts.Append(ExistingArtifacts); // Add an error report if none was received if ( InResults.State == EAutomationState::Fail && InResults.GetErrorTotal() == 0 ) { FString Msg = FString::Printf(TEXT("Test %s failed, but no errors were logged."), *TestInfo.GetFullTestPath()); Results[ClusterIndex][PassIndex].AddEvent(FAutomationEvent(EAutomationEventType::Error, *Msg)); } // While setting the results of the test cause the log of any selected test to refresh OnSetResults.ExecuteIfBound(AsShared()); } void FAutomationReport::AddArtifact(const int32 ClusterIndex, const int32 PassIndex, const FAutomationArtifact& Artifact) { //verify this is a platform this test is aware of check(( ClusterIndex >= 0 ) && ( ClusterIndex < Results.Num() )); check(( PassIndex >= 0 ) && ( PassIndex < Results[ClusterIndex].Num() )); LLM_SCOPE_BYNAME(TEXT("AutomationTest/Report")); Results[ClusterIndex][PassIndex].Artifacts.Add(Artifact); } void FAutomationReport::GetCompletionStatus(const int32 ClusterIndex, const int32 PassIndex, FAutomationCompleteState& OutCompletionState) { //if this test is enabled and a leaf test if (IsSupported(ClusterIndex) && (ChildReports.Num()==0)) { EAutomationState CurrentState = Results[ClusterIndex][PassIndex].State; //Enabled and In-Process if (IsEnabled()) { OutCompletionState.TotalEnabled++; if (CurrentState == EAutomationState::InProcess) { OutCompletionState.NumEnabledInProcess++; } } //Warnings if (Results[ClusterIndex][PassIndex].GetWarningTotal() > 0) { IsEnabled() ? OutCompletionState.NumEnabledTestsWarnings++ : OutCompletionState.NumDisabledTestsWarnings++; } //Test results if (CurrentState == EAutomationState::Success) { IsEnabled() ? OutCompletionState.NumEnabledTestsPassed++ : OutCompletionState.NumDisabledTestsPassed++; } else if (CurrentState == EAutomationState::Fail) { IsEnabled() ? OutCompletionState.NumEnabledTestsFailed++ : OutCompletionState.NumDisabledTestsFailed++; } else if( CurrentState == EAutomationState::Skipped ) { IsEnabled() ? OutCompletionState.NumEnabledTestsCouldntBeRun++ : OutCompletionState.NumDisabledTestsCouldntBeRun++; } } //recurse to children for (int32 ChildIndex = 0; ChildIndex < ChildReports.Num(); ++ChildIndex) { ChildReports[ChildIndex]->GetCompletionStatus(ClusterIndex,PassIndex, OutCompletionState); } } EAutomationState FAutomationReport::GetState(const int32 ClusterIndex, const int32 PassIndex) const { if ((ClusterIndex >= 0) && (ClusterIndex < Results.Num()) && (PassIndex >= 0) && (PassIndex < Results[ClusterIndex].Num())) { return Results[ClusterIndex][PassIndex].State; } return EAutomationState::NotRun; } void FAutomationReport::SetState(const EAutomationState State) { if (IsEnabled()) { for (int32 ResultsIndex = 0; ResultsIndex < Results.Num(); ++ResultsIndex) { for (int32 PassIndex = 0; PassIndex < Results[ResultsIndex].Num(); ++PassIndex) { Results[ResultsIndex][PassIndex].State = State; } } } } const FAutomationTestResults& FAutomationReport::GetResults( const int32 ClusterIndex, const int32 PassIndex ) { return Results[ClusterIndex][PassIndex]; } const int32 FAutomationReport::GetNumResults( const int32 ClusterIndex ) { return Results[ClusterIndex].Num(); } const int32 FAutomationReport::GetCurrentPassIndex( const int32 ClusterIndex ) { int32 PassIndex = 1; if( IsSupported(ClusterIndex) ) { for(; PassIndex < Results[ClusterIndex].Num(); ++PassIndex ) { if( Results[ClusterIndex][PassIndex].State == EAutomationState::NotRun ) { break; } } } return PassIndex - 1; } FString FAutomationReport::GetGameInstanceName( const int32 ClusterIndex ) { return Results[ClusterIndex][0].GameInstance; } TSharedPtr FAutomationReport::EnsureReportExists(FAutomationTestInfo& InTestInfo, const int32 ClusterIndex, const int32 NumPasses) { //Split New Test Name by the first "." found FString NameToMatch = InTestInfo.GetDisplayName(); FString FullPath = InTestInfo.GetFullTestPath(); FString NameRemainder; //if this is a leaf test (no ".") if (!InTestInfo.GetDisplayName().Split(TEXT("."), &NameToMatch, &NameRemainder)) { NameToMatch = InTestInfo.GetDisplayName(); } if ( NameRemainder.Len() != 0 ) { // Set the test info name to be the remaining string InTestInfo.SetDisplayName( NameRemainder ); // Update the fullpath FullPath.LeftChopInline(NameRemainder.Len() + 1); } uint32 NameToMatchHash = GetTypeHash(FullPath); TSharedPtr MatchTest; //check hash table first to see if it exists yet if (ChildReportNameHashes.Contains(NameToMatchHash)) { //go backwards. Most recent event most likely matches int32 TestIndex = ChildReports.Num() - 1; for (; TestIndex >= 0; --TestIndex) { //if the name matches if (ChildReports[TestIndex]->GetFullTestPath() == FullPath) { MatchTest = ChildReports[TestIndex]; break; } } } //if there isn't already a test like this if (!MatchTest.IsValid()) { LLM_SCOPE_BYNAME(TEXT("AutomationTest/Report")); if ( NameRemainder.Len() == 0 ) { // Create a new leaf node MatchTest = MakeShareable(new FAutomationReport(InTestInfo)); } else { // Create a parent node FAutomationTestInfo ParentTestInfo(NameToMatch, FullPath, TEXT(""), InTestInfo.GetTestFlags(), InTestInfo.GetNumParticipantsRequired()); MatchTest = MakeShareable(new FAutomationReport(ParentTestInfo, true)); } // Sort tests alphabetically const int32 InsertAt = Algo::UpperBound<>(ChildReports, MatchTest, [](const TSharedPtr& ReportA, const TSharedPtr& ReportB) { bool AIsLeafNode = !ReportA->IsParent(); bool BIsLeafNode = !ReportB->IsParent(); if (AIsLeafNode == BIsLeafNode) // both leaves or both parents => normal comparison { return ReportA->GetDisplayName() < ReportB->GetDisplayName(); } else // leaf and parent => A is less than B when B is the leaf { return BIsLeafNode; } } ); // Insert new test (sorted) ChildReports.Insert(MatchTest, InsertAt); ChildReportNameHashes.Add(NameToMatchHash, NameToMatchHash); } //mark this test as supported on a particular platform MatchTest->SetSupport(ClusterIndex); MatchTest->SetTestFlags( InTestInfo.GetTestFlags() ); MatchTest->SetNumParticipantsRequired( MatchTest->GetNumParticipantsRequired() > InTestInfo.GetNumParticipantsRequired() ? MatchTest->GetNumParticipantsRequired() : InTestInfo.GetNumParticipantsRequired() ); TSharedPtr FoundTest; //if this is a leaf node if (NameRemainder.Len() == 0) { FoundTest = MatchTest; } else { //recurse to add to the proper layer FoundTest = MatchTest->EnsureReportExists(InTestInfo, ClusterIndex, NumPasses); } return FoundTest; } TSharedPtr FAutomationReport::GetNextReportToExecute(bool& bOutAllTestsComplete, const int32 ClusterIndex, const int32 PassIndex, const int32 NumDevicesInCluster) { TSharedPtr NextReport; //if this is not a leaf node if (ChildReports.Num()) { for (int32 ReportIndex = 0; ReportIndex < ChildReports.Num(); ++ReportIndex) { NextReport = ChildReports[ReportIndex]->GetNextReportToExecute(bOutAllTestsComplete, ClusterIndex, PassIndex, NumDevicesInCluster); //if we found one, return it if (NextReport.IsValid()) { break; } } } else { //consider self if (IsEnabled() && IsSupported(ClusterIndex)) { EAutomationState TestState = GetState(ClusterIndex,PassIndex); //if any enabled test hasn't been run yet or is in process if (TestState == EAutomationState::NotRun || TestState == EAutomationState::InProcess) { //make sure we announce we are NOT done with all tests bOutAllTestsComplete = false; } if (TestState == EAutomationState::NotRun) { //Found one to run next NextReport = AsShared(); } } } return NextReport; } void FAutomationReport::GetEnabledTestReports(TArray>& OutReports) { //if this is not a leaf node if (ChildReports.Num()) { for (int32 ReportIndex = 0; ReportIndex < ChildReports.Num(); ++ReportIndex) { ChildReports[ReportIndex]->GetEnabledTestReports(OutReports); } } else { if (IsEnabled()) { //Found one to run OutReports.Add(AsShared()); } } } const bool FAutomationReport::HasErrors() { bool bHasErrors = false; for ( int32 ClusterIndex = 0; ClusterIndex < Results.Num(); ++ClusterIndex ) { for ( int32 PassIndex = 0; PassIndex < Results[ClusterIndex].Num(); ++PassIndex ) { //if we want tests with errors and this test had them OR we want tests warnings and this test had them if ( Results[ClusterIndex][PassIndex].GetErrorTotal() > 0 ) { //mark this test as having passed the results filter bHasErrors = true; break; } } } return bHasErrors; } const bool FAutomationReport::HasWarnings() { bool bHasWarnings = false; for ( int32 ClusterIndex = 0; ClusterIndex < Results.Num(); ++ClusterIndex ) { for ( int32 PassIndex = 0; PassIndex < Results[ClusterIndex].Num(); ++PassIndex ) { //if we want tests with errors and this test had them OR we want tests warnings and this test had them if ( Results[ClusterIndex][PassIndex].GetWarningTotal() > 0 ) { //mark this test as having passed the results filter bHasWarnings = true; break; } } } return bHasWarnings; } const bool FAutomationReport::GetDurationRange(float& OutMinTime, float& OutMaxTime) { //assume we haven't found any tests that have completed successfully OutMinTime = MAX_FLT; OutMaxTime = 0.0f; bool bAnyResultsFound = false; //keep sum of all child tests float ChildTotalMinTime = 0.0f; float ChildTotalMaxTime = 0.0f; for (int32 ReportIndex = 0; ReportIndex < ChildReports.Num(); ++ReportIndex) { float ChildMinTime = MAX_FLT; float ChildMaxTime = 0.0f; if (ChildReports[ReportIndex]->GetDurationRange(ChildMinTime, ChildMaxTime)) { ChildTotalMinTime += ChildMinTime; ChildTotalMaxTime += ChildMaxTime; bAnyResultsFound = true; } } //if any child test had valid timings if (bAnyResultsFound) { OutMinTime = ChildTotalMinTime; OutMaxTime = ChildTotalMaxTime; } for (int32 ClusterIndex = 0; ClusterIndex < Results.Num(); ++ClusterIndex ) { for( int32 PassIndex = 0; PassIndex < Results[ClusterIndex].Num(); ++PassIndex) { //if we want tests with errors and this test had them OR we want tests warnings and this test had them if( Results[ClusterIndex][PassIndex].State == EAutomationState::Success || Results[ClusterIndex][PassIndex].State == EAutomationState::Fail) { OutMinTime = FMath::Min(OutMinTime, Results[ClusterIndex][PassIndex].Duration ); OutMaxTime = FMath::Max(OutMaxTime, Results[ClusterIndex][PassIndex].Duration ); bAnyResultsFound = true; } } } return bAnyResultsFound; } const int32 FAutomationReport::GetNumDevicesRunningTest() const { return TestInfo.GetNumDevicesRunningTest(); } const int32 FAutomationReport::GetNumParticipantsRequired() const { return TestInfo.GetNumParticipantsRequired(); } void FAutomationReport::SetNumParticipantsRequired( const int32 NewCount ) { TestInfo.SetNumParticipantsRequired( NewCount ); } bool FAutomationReport::IncrementNetworkCommandResponses() { NumberNetworkResponsesReceived++; return (NumberNetworkResponsesReceived == TestInfo.GetNumParticipantsRequired()); } void FAutomationReport::ResetNetworkCommandResponses() { NumberNetworkResponsesReceived = 0; } const bool FAutomationReport::ExpandInUI() const { return bNodeExpandInUI; } void FAutomationReport::StopRunningTest() { if( IsEnabled() ) { for( int32 ResultsIndex = 0; ResultsIndex < Results.Num(); ++ResultsIndex ) { for( int32 PassIndex = 0; PassIndex < Results[ResultsIndex].Num(); ++PassIndex) { if( Results[ResultsIndex][PassIndex].State == EAutomationState::InProcess ) { Results[ResultsIndex][PassIndex].State = EAutomationState::NotRun; } } } } // Recurse to children for( int32 ChildIndex = 0; ChildIndex < ChildReports.Num(); ++ChildIndex ) { ChildReports[ChildIndex]->StopRunningTest(); } } bool FAutomationReport::IsToBeSkipped(FName* OutReason, bool* OutWarn) const { if (bNeedToSkip) { if (OutReason != nullptr) { if (!ExcludeTestInfo.Platforms.IsEmpty()) { *OutReason = *(SetToShortString(ExcludeTestInfo.Platforms) + TEXT(": ") + ExcludeTestInfo.Reason.ToString()); } else if (!ExcludeTestInfo.RHIs.IsEmpty()) { *OutReason = *(SetToShortString(ExcludeTestInfo.RHIs) + TEXT(": ") + ExcludeTestInfo.Reason.ToString()); } else { *OutReason = ExcludeTestInfo.Reason; } } if (OutWarn != nullptr) { *OutWarn = ExcludeTestInfo.Warn; } return true; } return false; } bool FAutomationReport::IsToBeSkippedOnConditions() const { return bNeedToSkip && ExcludeTestInfo.HasConditions(); } bool FAutomationReport::IsToBeSkippedByPropagation() const { return bNeedToSkip && ExcludeTestInfo.bIsPropagated; } void FAutomationReport::SetSkipFlag(bool bEnableSkip, const FAutomationTestExcludelistEntry* Template, bool bFromPropagation) { if (IsToBeSkipped() == bEnableSkip) { if (!bEnableSkip || Template == nullptr) return; if (!bFromPropagation) { // Remove previous entry in the config UAutomationTestExcludelist::Get()->RemoveFromExcludeTest(TestInfo.GetFullTestPath()); } } if (!bFromPropagation && !ExcludeTestInfo.IsEmpty() && ExcludeTestInfo.bIsPropagated && !ExcludeTestInfo.HasConditions()) return; // Propagated exclusion can't be changed directly bNeedToSkip = bEnableSkip; if (Template != nullptr) { // Update the entry ExcludeTestInfo = *Template; ExcludeTestInfo.bIsPropagated = bFromPropagation; } auto ExcludedTestCached = UAutomationTestExcludelist::Get(); if (bFromPropagation) { auto Entry = ExcludedTestCached->GetExcludeTestEntry(TestInfo.GetFullTestPath()); if (bNeedToSkip) { // If we get an exclusion entry, check if it is the original one. if (Entry != nullptr) { ExcludeTestInfo = *Entry; ExcludeTestInfo.SetPropagation(TestInfo.GetFullTestPath()); } } else { // Before enabling the test, check if there is an underlying exclusion already set if (Entry == nullptr) { ExcludeTestInfo.Reset(); } else { // Update instance exclusion info ExcludeTestInfo = *Entry; bNeedToSkip = true; // Update exclusion template for the children propagation Template = Entry; } } } else { if (bNeedToSkip) { check(Template != nullptr); auto Entry = ExcludedTestCached->GetExcludeTestEntry(TestInfo.GetFullTestPath()); if (Entry != nullptr && ExcludeTestInfo.RemoveConditions(*Entry)) { // Branch off the template with the difference exclusion condition set // Remove the overlapping conditions as they are redundant since the parent has precedence Template = &ExcludeTestInfo; } ExcludedTestCached->AddToExcludeTest(TestInfo.GetFullTestPath(), *Template); } else { ExcludedTestCached->RemoveFromExcludeTest(TestInfo.GetFullTestPath()); auto Entry = ExcludedTestCached->GetExcludeTestEntry(TestInfo.GetFullTestPath()); if (Entry != nullptr) { // If there is still an entry, it means a higher exclusion rule exists so we apply it. // Update instance exclusion info ExcludeTestInfo = *Entry; ExcludeTestInfo.bIsPropagated = true; bNeedToSkip = true; // Update exclusion template for the children propagation Template = Entry; } } } // Propagate to children if (IsParent()) { for (IAutomationReportPtr Child : GetChildReports()) { Child->SetSkipFlag(bNeedToSkip, Template, true); } } if (!bFromPropagation) { // Save config only at the end of the recursion ExcludedTestCached->SaveToConfigs(); } } TSharedPtr FAutomationReport::GetExcludeOptions() { ExcludeTestInfo.Test = *TestInfo.GetFullTestPath(); return ExcludeTestInfo.GetOptions(); }