Files
UnrealEngineUWP/Engine/Source/Developer/AutomationController/Private/AutomationReport.cpp
Nick Darnell 1258799218 Copying //UE4/Dev-Editor to //UE4/Dev-Main (Source: //UE4/Dev-Editor @ 2973866)
#lockdown Nick.Penwarden

==========================
MAJOR FEATURES + CHANGES
==========================

Change 2937390 on 2016/04/07 by Cody.Albert

	#jira UE-29211
	Fixed slider to properly bubble unhandled OnKeyDown events

Change 2939672 on 2016/04/11 by Richard.TalbotWatkin

	Made a change to how file check out notifications work. Now the dirty package state is processed at the end of every tick, meaning that packages which are dirtied and then cleaned again are not processed. This fixes an issue where a number of child blueprints were flagged as needing checkout when a parent blueprint was compiled.  This also allows multiple packages which are dirtied at the same time to be treated as one transaction.
	#jira UE-29193 - "Files need check-out" prompt spams Blueprint users

Change 2939686 on 2016/04/11 by Richard.TalbotWatkin

	A number of further improvements to mesh vertex color painting:
	* Lower LODs are now automatically fixed up for instances which were created in a previous bugged version of the engine.
	* Since lower LODs cannot currently have their vertex colors edited, their vertex colors are always derived from LOD0.
	* Fixed a bug when building lower LODs so that vertices in neighboring octree nodes are considered when looking for the nearest vertex from LOD0 which corresponds.
	* Fixed issue where static meshes with imported LODs would not have the lower LODs' override colors set when "Copy instance vertex colors to source mesh" was used (static meshes with generated LODs were always getting correct override colors).
	#jira UE-28563 - Incorrectly displayed LOD VertexColor until paint mode is selected

Change 2939906 on 2016/04/11 by Nick.Darnell

	Automation - Adding several enhancements to the automation framework and improving the UI.
	* Tests in the UI now have a link to the source and line where they orginate.
	* There's now a general purpose latent lambda command you can use to run arbitrary code latently.
	* Added Inlined AddCommand for regular and networked commands to the base automation class, to avoid the use of the macro, which prevents breakpoints from working in lambda code.
	* Front end now has better column displays offering more room to the test name
	* Changed several events to the automation controller to multicast delegates so that many could hook them.
	* The UI now refreshes the selection after tests finish so that the output log updates.

Change 2939908 on 2016/04/11 by Nick.Darnell

	Automation - The editor import/export tests are now a complex test and actually sperate out all the tests that can be run, some trickiness was required on the filenames so that they didn't expand into more child tests in the UI. (replacing .'s with _'s)

Change 2940028 on 2016/04/11 by Nick.Darnell

	Automation - Removing the search box from the toolbar.  It's now inlined above the test tree.  Tweaking the padding to make it look more other windows and make everything not look so squished.  Recursive expansion now works on tests.

Change 2940066 on 2016/04/11 by Nick.Darnell

	Automation - Moving the filter group dropdown out of the toolbar and onto the line with the search box above the treeview - additional tweaks to it.

Change 2940092 on 2016/04/11 by Jamie.Dale

	PR #2248: Datatable select next row (Contributed by FineRedMist)

Change 2940093 on 2016/04/11 by Jamie.Dale

	PR #2248: Datatable select next row (Contributed by FineRedMist)

Change 2940157 on 2016/04/11 by Jamie.Dale

	Fixing FTextTest due to some changes made to how currency is formatted

Change 2940694 on 2016/04/12 by Richard.TalbotWatkin

	Fixed issue where vertex override colors were not being propagated correctly for generated lower LODs.
	#jira UE-29360 - Override Colors not propagated correctly to generated lower LODs

Change 2942379 on 2016/04/13 by Richard.TalbotWatkin

	Fixed issue where entering PIE while selecting an actor in Mesh Paint mode could lead to a MeshPaintStaticMeshAdapter holding onto an invalid pointer to an old mesh component, and causing a crash upon leaving the mode.  This can happen because, when loading a new streaming level, the proxy actor can be selected when starting PIE, which will subsequently be added to the tool's internal lists.  This needs to be added as a GC reference so that it can be NULLed when forcibly destroyed.
	#jira UE-29345 - Crash occurs exiting the editor after enabling mesh paint mode and PIEing

Change 2942947 on 2016/04/13 by Richard.TalbotWatkin

	Fixed crash when pasting a material function call node from one project to another in which it is not defined.
	#jira UE-27087 - Crash when pasting MaterialFunctionCall expressions into the material editor between projects

Change 2943452 on 2016/04/14 by Richard.TalbotWatkin

	Updated F4 debug key binding to match what's in ShowFlags.cpp
	PR #2197 (contributed by mfortin-bhvr)

Change 2943824 on 2016/04/14 by Alexis.Matte

	#jira UE-29090
	Make sure we cannot open the color picker when a property is edit const

Change 2943841 on 2016/04/14 by Alexis.Matte

	#jira UE-28924
	tooltip was add for every hierarchy import option

Change 2943927 on 2016/04/14 by Alexis.Matte

	#jira UE-29423
	Add Obj support for scene importer

	Github PR #2272

Change 2943967 on 2016/04/14 by Richard.TalbotWatkin

	Added relevant fields from FBodyInstance to the FoliageType customizations.
	#jira UE-20138 - FoliageType has a FBodyInstance but only shows Collision Presets and not other FBodyInstance properties

Change 2948397 on 2016/04/19 by Andrew.Rodham

	Moved FSlateIcon definition to SlateCore

	It was previously declared as SLATE_API, despite its header residing inside SlateCore. Reviewed by Jamie Dale.

Change 2948805 on 2016/04/19 by Andrew.Rodham

	Editor: Deprecated FName UEdGraphNode::GetPaletteIcon(FLinearColor&); in favor of FSlateIcon UEdGraphNode::GetIconAndTint(FLinearColor&); to allow for icons in external style sets to be used.

	  - Previously, all icons were assumed to reside within FEditorStyle, which is not the case and would create broken icons in the graph editor. All relevant code has been updated to use FSlateIcon structures instead of a simple name.
	  - This change required a significant overhaul to FClassIconFinder to support FSlateIcons. To keep the API clean, FSlateIconFinder now deals with FSlateIcon class icon finding operations, and FClassIconFinder for the most part just adds actor specific logic.

	#jira UE-26502

Change 2950658 on 2016/04/20 by Alexis.Matte

	#jira UE-24333
	Skinxx workflow, we now output an error if there is mix of material with skinxx and some with no skinxx suffix

Change 2950663 on 2016/04/20 by Alexis.Matte

	#jira UE-29582
	When exporting to fbx we have to export each material instance as one fbx material

Change 2951240 on 2016/04/21 by Alexis.Matte

	#jira UE-28473
	Make sure light are render properly after importing a fbx scene

Change 2951421 on 2016/04/21 by Alexis.Matte

	#jira UE-29773
	fbx skeletalmesh import now support mesh hierarchy

Change 2955873 on 2016/04/26 by Richard.TalbotWatkin

	PR #2225: Fix working package directory from the launch profiles (Contributed by projectgheist)

Change 2955965 on 2016/04/26 by Nick.Darnell

	Merging //UE4/Dev-Main to Dev-Editor (//UE4/Dev-Editor)

Change 2956717 on 2016/04/26 by Andrew.Rodham

	Editor: World Outliner now correctly calls ProcessEditDelete on editor modes that have asked to process delete operations

	#jira UE-26968

Change 2956822 on 2016/04/26 by Andrew.Rodham

	Editor: Fixed actors not being removed from the scene outliner when they are added and removed on the same frame

	#jira UE-7777

Change 2956931 on 2016/04/26 by Nick.Darnell

	New Module - UATHelper - Moving the UAT launching code from the MainFrame module into a reusable module other modules can trigger.

Change 2956932 on 2016/04/26 by Nick.Darnell

	Plugins - Now allowing you to package a plugin from the plugin browsing view.  Still work in progress.

Change 2957164 on 2016/04/26 by Nick.Darnell

	Hot Reload - Fixing hot reload, it no longer creates a temporary copy of the module manager.  Making the copy constructor private on the module manager to prevent this in the future.

Change 2957165 on 2016/04/26 by Nick.Darnell

	Fixing the Editor Mode plugin sample, it no longer provides a bad starting example for where to create your widgets.

	#jira UE-28456

Change 2957510 on 2016/04/27 by Nick.Darnell

	PR #2198: Git Plugin implement the Sync operation to update local files using the git pull --rebase command (Contributed by SRombauts)

	#jira UE-28763

Change 2957511 on 2016/04/27 by Andrew.Rodham

	Editor: Make favorites button on details panel non-focusable
	  - This was preventing users being able to tab between value fields on the details panel

Change 2957610 on 2016/04/27 by Nick.Darnell

	PR #1836: Git plugin: make initial commit when initializing new project (Contributed by SRombauts)

	#jira UE-24190

Change 2957667 on 2016/04/27 by Jamie.Dale

	Fixed crash that could happen in FTextLayout::GetLineViewIndexForTextLocation if passed a bad location

	#jira OR-18634

Change 2958035 on 2016/04/27 by Nick.Darnell

	Fixing the DesignerRebuild flag detection so that we can just refresh the slate widget without recreating the preview UObject, which causes the destruction of the details panel, and the slate widget recreation was the only part that was required.

Change 2958272 on 2016/04/27 by Jamie.Dale

	Added FAssetData::GetTagValue to handle getting asset tag values in a type-correct way

	This allows type-conversion using LexicalConversion, and also has specializations for FString, FText, and FName.

	#jira UE-12096

Change 2958348 on 2016/04/27 by Jamie.Dale

	PR #2282: Slate font shutdown order fix (Contributed by FineRedMist)

Change 2958352 on 2016/04/27 by Jamie.Dale

	Fixed the subtitle manager updating the wrong list of subtitles

	#jira UE-29511

Change 2958390 on 2016/04/27 by Jamie.Dale

	Removed some old placement-new style array insertions

Change 2959360 on 2016/04/28 by Richard.TalbotWatkin

	Fixed potential crash when mesh painting actors whose geometry adapters are no longer registered.
	#jira UE-29615 - [CrashReport] UE4Editor_MeshPaint!FEdModeMeshPaint::DoPaint() [meshpaintedmode.cpp:1127]

Change 2959724 on 2016/04/28 by Cody.Albert

	Merging hardware survey gating logic from 4.10

	#jira UE-28666

Change 2959807 on 2016/04/28 by Cody.Albert

	Removed deprecated function call

	#jira UE-28666

Change 2959894 on 2016/04/28 by Cody.Albert

	Fix for scroll offset being clamped by content size, not scroll max

	#jira UE-20676

Change 2960048 on 2016/04/28 by Jamie.Dale

	Added FAssetData::GetTagValueRef to go along with FAssetData::GetTagValue

	#jira UE-12096

Change 2960782 on 2016/04/29 by Jamie.Dale

	Updating code to use the new FText aware asset registry tag functions

	#jira UE-12096

Change 2960885 on 2016/04/29 by Jamie.Dale

	Updating code to use the new FText aware asset registry tag functions

	#jira UE-12096

Change 2961170 on 2016/04/29 by Jamie.Dale

	Updating code to use the new FText aware asset registry tag functions

	#jira UE-12096

Change 2961171 on 2016/04/29 by Jamie.Dale

	Updating code to use the new FText aware asset registry tag functions

	#jira UE-12096

Change 2961173 on 2016/04/29 by Jamie.Dale

	Removed some inline duplication on the specialized template functions

	#jira UE-12096

Change 2963124 on 2016/05/02 by Jamie.Dale

	FExternalDragOperation can now contain both text and file data at the same time

	This better mirrors what the OS level drag-and-drop operations are capable of, and some applications will actually give you both bits of data at the same time.

	#jira UE-26585

Change 2963175 on 2016/05/02 by Jamie.Dale

	Updated some font editor tooltips to be more descriptive

	#jira UE-17429

Change 2963290 on 2016/05/02 by Jamie.Dale

	The Localise UAT command can now be run with a null localisation provider

Change 2963305 on 2016/05/02 by Jamie.Dale

	Fixed minor typo

Change 2963402 on 2016/05/02 by Jamie.Dale

	Cleaned up all the current localization key conflicts and warnings from gathering Engine code

	#jira UE-25833

Change 2963415 on 2016/05/02 by Jamie.Dale

	Rephrased a message that could generate a CIS warning

	#jira UE-25833

Change 2964184 on 2016/05/03 by Jamie.Dale

	Fixed duplicate "Font" entry in asset picker menu

	This was caused by PropertyCustomizationHelpers::GetNewAssetFactoriesForClasses using CanCreateNew rather than ShouldShowInNewMenu, as UFont has two factories, but one is supposed to be hidden from the UI.

	We also now make sure the factories are sorted by display name before being shown in the UI.

	#jira UE-24903

Change 2966108 on 2016/05/04 by Nick.Darnell

	Engine - Rearranging the order of ELoadingPhase's enums so that they match the loading order of modules.

Change 2966113 on 2016/05/04 by Nick.Darnell

	[Engine Loop Change] UEngine now defines a Start() function, that subclasses can use to start game related things after initialization of the engine.  This is done so that after the Init() call on UEngine, we can then perform a module load for the ELoadingPhase::PostEngineInit phase of loading, then inform the UEngine that it's time to start the game.  Therefore, UGameEngine now tells the GameInstance to Start during this phase now.

Change 2966121 on 2016/05/04 by Jamie.Dale

	Config writing improvements when dealing with property values

	This updates FConfigFile::ShouldExportQuotedString to make sure that a property value containing any characters that FParse::LineExtended will consume when parsing back in the config file (such as { and }, or a trailing \) cause the string to be quoted.

	This also adds FConfigFile::GenerateExportedPropertyLine to generate the INI key->value lines in a consistent and correctly escaped way, and makes sure that everything that writes out lines to a config file uses it.

	FConfigCacheIni::SetString and FConfigCacheIni::SetText have been updated to update the value even if it only differs by case.

	UObject::SaveConfig and UObject::LoadConfig have had some code whitespace fix-up (from a bad merge).

Change 2966122 on 2016/05/04 by Jamie.Dale

	Added a setting to control dialogue wave audio filenames

Change 2966481 on 2016/05/04 by Jamie.Dale

	PR #2336: BUGFIX: Selection of objects in the Content browser from WorldSettings (Contributed by projectgheist)

Change 2966887 on 2016/05/04 by Jamie.Dale

	PR #2336: BUGFIX: Selection of objects in the Content browser from WorldSettings (Contributed by projectgheist)

Change 2967488 on 2016/05/05 by Ben.Marsh

	Changes to support packaging plugins from the editor.

	* UBT now has an option to explicitly disable hot-reloading in any circumstances.
	* When running with -module arguments for a monolithic target, UBT will no longer try to relink the executable in source builds (so it's possible to compile plugin libs outside of an installed engine build without having already built UE4Game).
	* When packaging, a temporary host project is always generated in the output directory to avoid invalidating intermediates in the source directory.
	* An empty Config\FilterPlugin.ini file is written out with instructions on how to list additional files to package if it is not already present.

Change 2967947 on 2016/05/05 by Nick.Darnell

	PR #2358: Properly display Mip Level Count and Format for UTexture2DDynamic Textures (Contributed by Allegorithmic)

	#jira UE-30371

Change 2968333 on 2016/05/05 by Jamie.Dale

	Fixed MultiLine not working with arrays of string or text properties

	- The detail customizations for FString and FText properties now read the meta-data off the correct property.
	- The UDS editor now lets you set the "MultiLine" meta-data on arrays of FString and FText properties.
	- Fixed changing the "MultiLine" flag on a UDS property not rebuilding the default value editor.
	- Fixed the default values panel in the UDS editor having a title area.

	#jira UE-30392

Change 2968999 on 2016/05/06 by Jamie.Dale

	Fixed infinite loop in the editor if a directory that is being watched is deleted

	#jira UE-30172

Change 2969105 on 2016/05/06 by Richard.TalbotWatkin

	Fixed issue where opening a submenu while the parent menu had a text box focused would lead to a crash. The graph node comment text widget now only dismisses all menus if the text commit info implies that it was committed by some user action.
	#jira UE-29086 - Crash When Typing a Node Comment and Hovering Over the Alignment Option

Change 2969440 on 2016/05/06 by Jamie.Dale

	Significant performance improvements when pasting a large amount of text

	#jira UE-19712

Change 2969619 on 2016/05/06 by Andrew.Rodham

	Auto-reimport is now disabled inside an editor running in unattended mode

Change 2969621 on 2016/05/06 by Jamie.Dale

	Added the ability to override the subtitle used on a dialogue wave

	This is useful for effort sounds, plus some other cases, such as characters speaking in a foreign language not known to the player.

	#jira UETOOL-795

Change 2970588 on 2016/05/09 by Chris.Wood

	Fix typo in operator expression in UEndUserSettings::SetSendAnonymousUsageDataToEpic()
	[UE-26958] - GitHub 2056 : Fixing typo in the operator
	#2056

Change 2971151 on 2016/05/09 by Chris.Wood

	Logging ensure fails as errors. Automated tests with ensure fails will be unsuccessful.
	[UE-19579] - If an ensure() fails within an automated test, the test can still show a positive result.
	[UE-26575] - GitHub 2030 : Add error-severity message to log on ensure.

	PR #2030

Change 2971267 on 2016/05/09 by Alexis.Matte

	Wrong parameter when calling GetImportOptions

	#jira UE-30299

Change 2972073 on 2016/05/10 by Richard.TalbotWatkin

	Fixed UModel methods which make surfaces as modified.
	#jira UE-28831 - Unable to undo material placement on BSP

Change 2972329 on 2016/05/10 by Nick.Darnell

	Merging //UE4/Dev-Main to Dev-Editor (//UE4/Dev-Editor)

Change 2972887 on 2016/05/10 by Alexis.Matte

	#jira UE-30167
	We now import the geometric transform also when we uncheck the absolute transform in the vertex.

Change 2973664 on 2016/05/11 by Nick.Darnell

	Merging //UE4/Dev-Main to Dev-Editor (//UE4/Dev-Editor)

Change 2973717 on 2016/05/11 by Nick.Darnell

	Fixing compiler issues from main merge.

	#jira UE-30590

Change 2973846 on 2016/05/11 by Jamie.Dale

	Exposed FConfigValue::ExpandValue and added FConfigValue::CollapseValue

	These are both static and can be used to expand or collapse the macros used in our config files (mostly when dealing with paths), in code that has to deal with the config system, but isn't internal to the config system (mostly things that deal with default configs outside of UObjects).

	The old non-static version of FConfigValue::ExpandValue is now FConfigValue::ExpandValueInternal, which just calls FConfigValue::ExpandValue on SavedValue and ExpandedValue.

	This also changes some code that was using FString.Replace to use FString.ReplaceInline. This reduces allocations, and also allows us to avoid another string comparison to see whether the strings are identical (as ReplaceInline returns the number of replacements that were made).

Change 2973847 on 2016/05/11 by Jamie.Dale

	Changing the loading phase in the localization dashboard now writes to the default config

	#jira UE-30482

Change 2973866 on 2016/05/11 by Jamie.Dale

	Deprecated some functions that were taking an unused position.

	These unused parameters caused confusion and lead to UE-30276. The old versions have been deprecated, and new versions without those parameters have been added. Existing code has been updated to call the non-deprecated version.

	- FViewportFrame::ResizeFrame
	- FSceneViewport::ResizeFrame
	- FSceneViewport::ResizeViewport

[CL 2973886 by Nick Darnell in Main branch]
2016-05-11 11:05:13 -04:00

959 lines
26 KiB
C++

// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved.
#include "AutomationControllerPrivatePCH.h"
FAutomationReport::FAutomationReport(FAutomationTestInfo& InTestInfo, bool InIsParent)
: bEnabled( false )
, bIsParent(InIsParent)
, bNodeExpandInUI(false)
, bSelfPassesFilter(false)
, SupportFlags(0)
, TestInfo( InTestInfo )
, bTrackingHistory(false)
, NumRecordsToKeep(0)
{
// Enable smoke tests
if ( TestInfo.GetTestFlags() == EAutomationTestFlags::SmokeFilter )
{
bEnabled = true;
}
if (!bIsParent)
{
LoadHistory();
}
}
void FAutomationReport::Empty()
{
//release references to all child tests
ChildReports.Empty();
ChildReportNameHashes.Empty();
FilteredChildReports.Empty();
}
FString FAutomationReport::GetAssetName() const
{
return TestInfo.GetTestParameter();
}
FString FAutomationReport::GetCommand() const
{
return TestInfo.GetTestName();
}
const FString& FAutomationReport::GetDisplayName() const
{
return TestInfo.GetDisplayName();
}
FString FAutomationReport::GetDisplayNameWithDecoration() const
{
FString FinalDisplayName = TestInfo.GetDisplayName();
//if this is an internal leaf node and the "decoration" name is being requested
if (ChildReports.Num())
{
int32 NumChildren = GetTotalNumChildren();
//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;
}
void FAutomationReport::GetEnabledTestNames(TArray<FString>& OutEnabledTestNames, FString CurrentPath) const
{
//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::SetEnabledTests(const TArray<FString>& EnabledTests, 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 = EnabledTests.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(EnabledTests,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<<ClusterIndex);
//ensure there is enough room in the array for status per platform
for (int32 i = 0; i <= ClusterIndex; ++i)
{
//Make sure we have enough results for a single pass
TArray<FAutomationTestResults> AutomationTestResult;
AutomationTestResult.Add( FAutomationTestResults() );
Results.Add( AutomationTestResult );
}
}
bool FAutomationReport::IsSupported(const int32 ClusterIndex) const
{
return (SupportFlags & (1<<ClusterIndex)) ? true : false;
}
uint32 FAutomationReport::GetTestFlags( ) const
{
return TestInfo.GetTestFlags();
}
FString FAutomationReport::GetSourceFile() const
{
return TestInfo.GetSourceFile();
}
int32 FAutomationReport::GetSourceFileLine() const
{
return TestInfo.GetSourceFileLine();
}
void FAutomationReport::SetTestFlags( const uint32 InTestFlags)
{
TestInfo.AddTestFlags( InTestFlags );
if ( InTestFlags == EAutomationTestFlags::SmokeFilter )
{
bEnabled = true;
}
}
const bool FAutomationReport::IsParent()
{
return bIsParent;
}
const bool FAutomationReport::IsSmokeTest( )
{
return GetTestFlags( ) & EAutomationTestFlags::SmokeFilter ? true : false;
}
bool FAutomationReport::SetFilter( TSharedPtr< AutomationFilterCollection > InFilter, const bool ParentPassedFilter )
{
//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 ) );
//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 || bSelfPassesFilter || ParentPassedFilter )
{
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<TSharedPtr<IAutomationReport> >& FAutomationReport::GetFilteredChildren()
{
return FilteredChildReports;
}
TArray<TSharedPtr<IAutomationReport> >& FAutomationReport::GetChildReports()
{
return ChildReports;
}
void FAutomationReport::ClustersUpdated(const int32 NumClusters)
{
TestInfo.ResetNumDevicesRunningTest();
//Fixup Support flags
SupportFlags = 0;
for (int32 i = 0; i <= NumClusters; ++i)
{
SupportFlags |= (1<<i);
}
//Fixup Results array
if( NumClusters > Results.Num() )
{
for( int32 ClusterIndex = Results.Num(); ClusterIndex < NumClusters; ++ClusterIndex )
{
//Make sure we have enough results for a single pass
TArray<FAutomationTestResults> 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].State = EAutomationState::NotRun;
Results[ClusterIndex][PassIndex].Warnings.Empty();
Results[ClusterIndex][PassIndex].Errors.Empty();
}
}
}
//recurse to children
for (int32 ChildIndex = 0; ChildIndex < ChildReports.Num(); ++ChildIndex)
{
ChildReports[ChildIndex]->ResetForExecution(NumTestPasses);
}
}
void FAutomationReport::TrackHistory(const bool bShouldTrack, const int32 NumReportsToTrack)
{
bTrackingHistory = bShouldTrack;
NumRecordsToKeep = NumReportsToTrack;
if (bTrackingHistory && ChildReports.Num() == 0)
{
LoadHistory();
}
//recurse to children
for (auto& NextChildReport : ChildReports)
{
NextChildReport->TrackHistory(bTrackingHistory, NumRecordsToKeep);
}
}
void FAutomationReport::AddToHistory()
{
// Dictate the file path we are writing this run as history to.
const FDateTime FileDate = FDateTime::Now();
const FString FileName = GetDisplayName() + FileDate.ToString() + TEXT(".log");
const FString FileLocation = FPaths::ConvertRelativePathToFull(FPaths::AutomationLogDir()) + GetDisplayName();
const FString FullPath = FPaths::Combine(*FileLocation, *FileName);
// Write any Errors and Warnings to the log, if none, then simply report that it was successful.
if (FArchive* LogFile = IFileManager::Get().CreateFileWriter(*FullPath))
{
bool bExportedAnyErrors = false;
for (int32 ClusterIndex = 0; ClusterIndex < Results.Num(); ++ClusterIndex)
{
for (int32 PassIndex = 0; PassIndex < Results[ClusterIndex].Num(); ++PassIndex)
{
for (int32 ErrorIndex = 0; ErrorIndex < Results[ClusterIndex][PassIndex].Errors.Num(); ++ErrorIndex)
{
if (!bExportedAnyErrors)
{
bExportedAnyErrors = true;
FString ErrorIdentifier(TEXT("<<ERRORS>>"));
ErrorIdentifier += LINE_TERMINATOR;
LogFile->Serialize(TCHAR_TO_ANSI(*ErrorIdentifier), ErrorIdentifier.Len());
}
FString NextError = Results[ClusterIndex][PassIndex].Errors[ErrorIndex] + LINE_TERMINATOR;
LogFile->Serialize(TCHAR_TO_ANSI(*NextError), NextError.Len());
}
}
}
bool bExportedAnyWarnings = false;
for (int32 ClusterIndex = 0; ClusterIndex < Results.Num(); ++ClusterIndex)
{
for (int32 PassIndex = 0; PassIndex < Results[ClusterIndex].Num(); ++PassIndex)
{
for (int32 WarningIndex = 0; WarningIndex < Results[ClusterIndex][PassIndex].Warnings.Num(); ++WarningIndex)
{
if (!bExportedAnyWarnings)
{
bExportedAnyWarnings = true;
FString WarningIdentifier(TEXT("<<WARNINGS>>"));
WarningIdentifier += LINE_TERMINATOR;
LogFile->Serialize(TCHAR_TO_ANSI(*WarningIdentifier), WarningIdentifier.Len());
}
const FString NextWarning = Results[ClusterIndex][PassIndex].Warnings[WarningIndex] + LINE_TERMINATOR;
LogFile->Serialize(TCHAR_TO_ANSI(*NextWarning), NextWarning.Len());
}
}
}
if (bExportedAnyErrors == false && bExportedAnyWarnings == false)
{
FString SuccessIdentifier(TEXT("<<SUCCESS>>"));
SuccessIdentifier += LINE_TERMINATOR;
LogFile->Serialize(TCHAR_TO_ANSI(*SuccessIdentifier), SuccessIdentifier.Len());
}
LogFile->Close();
delete LogFile;
// Cache an automation history item for tracking in this session
TSharedRef<FAutomationHistoryItem> HistoryItem = MakeShareable(new FAutomationHistoryItem);
HistoryItem->LogLocation = FileName;
HistoryItem->RunDate = FileDate;
HistoryItem->RunResult =
(bExportedAnyErrors ? FAutomationHistoryItem::EAutomationHistoryResult::Errors :
(bExportedAnyWarnings ? FAutomationHistoryItem::EAutomationHistoryResult::Warnings :
FAutomationHistoryItem::EAutomationHistoryResult::Successful));
HistoryItems.Add(HistoryItem);
}
}
void FAutomationReport::MaintainHistory(TArray<FString>& InLogFiles)
{
// Find all the logs in this reports log location
const FString LogsLocation = FPaths::ConvertRelativePathToFull(FPaths::AutomationLogDir()) + GetDisplayName();
// Sort the logs in reverse chronological order
struct FLogSortPredicate
{
FString DisplayName;
FLogSortPredicate(const FString& InDisplayName) : DisplayName(InDisplayName) {}
/** Sort predicate operator */
bool operator ()(FString LHS, FString RHS) const
{
FString LogExt = TEXT(".log");
FString LHSDateStr = LHS.RightChop(DisplayName.Len());
LHSDateStr = LHSDateStr.LeftChop(LogExt.Len());
FDateTime LHSDate;
FDateTime::Parse(LHSDateStr, LHSDate);
FString RHSDateStr = RHS.RightChop(DisplayName.Len());
RHSDateStr = RHSDateStr.LeftChop(LogExt.Len());
FDateTime RHSDate;
FDateTime::Parse(RHSDateStr, RHSDate);
return LHSDate > RHSDate;
}
};
InLogFiles.Sort(FLogSortPredicate(GetDisplayName()));
// For logs, we keep the number equal to AutomationReportConstants::MaximumLogsToKeep around.
// This will mean that we can extend or history to see when changed within the report
for (int32 LogIndex = InLogFiles.Num() - 1; LogIndex >= AutomationReportConstants::MaximumLogsToKeep; LogIndex--)
{
check(IFileManager::Get().Delete(*FPaths::Combine(*LogsLocation, *InLogFiles[LogIndex])));
}
// Sort the history items in reverse chronological order
struct FHistorySortPredicate
{
FHistorySortPredicate() {}
/** Sort predicate operator */
bool operator ()(const TSharedPtr<FAutomationHistoryItem>& LHS, const TSharedPtr<FAutomationHistoryItem>& RHS) const
{
check(LHS.IsValid() && RHS.IsValid());
return LHS->RunDate > RHS->RunDate;
}
};
HistoryItems.Sort(FHistorySortPredicate());
for (int32 ItemIndex = HistoryItems.Num() - 1; ItemIndex >= NumRecordsToKeep; ItemIndex--)
{
HistoryItems.RemoveAt(ItemIndex);
}
}
void FAutomationReport::LoadHistory()
{
// Clear out the previous results before we rebuild our list
HistoryItems.Empty();
// Load the logs from this reports automation log location
const FString LogsLocation = FPaths::ConvertRelativePathToFull(FPaths::AutomationLogDir()) + GetDisplayName();
TArray<FString> LogFiles;
IFileManager::Get().FindFiles(LogFiles, *(LogsLocation / "*.log"), true, false);
for (FString& NextLogFile : LogFiles)
{
FString FileContents;
if (FFileHelper::LoadFileToString(FileContents, *FPaths::Combine(*LogsLocation, *NextLogFile)))
{
TSharedRef<FAutomationHistoryItem> HistoryItem = MakeShareable(new FAutomationHistoryItem);
// Cache the log location
HistoryItem->LogLocation = NextLogFile;
// Parse the date and time from the log name
{
FString LogExt = TEXT(".log");
FString DateStr = NextLogFile.RightChop(GetDisplayName().Len());
DateStr = DateStr.LeftChop(LogExt.Len());
FDateTime::Parse(DateStr, HistoryItem->RunDate);
}
// Parse whether the previous runs had errors, warnings or were successful
{
if (FileContents.StartsWith(TEXT("<<ERRORS>>")))
{
HistoryItem->RunResult = FAutomationHistoryItem::EAutomationHistoryResult::Errors;
}
else if (FileContents.StartsWith(TEXT("<<WARNINGS>>")))
{
HistoryItem->RunResult = FAutomationHistoryItem::EAutomationHistoryResult::Warnings;
}
else if (FileContents.StartsWith(TEXT("<<SUCCESS>>")))
{
HistoryItem->RunResult = FAutomationHistoryItem::EAutomationHistoryResult::Successful;
}
}
// Add our log to the tracking
HistoryItems.Add(HistoryItem);
}
}
// Do a pass on the existing logs for any we no longer wish to maintain.
if (LogFiles.Num())
{
MaintainHistory(LogFiles);
}
}
const TArray<TSharedPtr<FAutomationHistoryItem>>& FAutomationReport::GetHistory() const
{
return HistoryItems;
}
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();
}
Results[ ClusterIndex ][ PassIndex ] = InResults;
// Add an error report if none was received
if ( InResults.State == EAutomationState::Fail && InResults.Errors.Num() == 0 && InResults.Warnings.Num() == 0 )
{
Results[ClusterIndex][PassIndex].Errors.Add( "No Report Generated" );
}
// If we are tracking history, then export it.
if (bTrackingHistory && (InResults.State == EAutomationState::Success || InResults.State == EAutomationState::Fail))
{
AddToHistory();
//Remove find files as it was too expensive. And definitely too expensive for just updating one test
//MaintainHistory();
}
}
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::Type CurrentState = Results[ClusterIndex][PassIndex].State;
//Enabled and In-Process
if (IsEnabled())
{
OutCompletionState.TotalEnabled++;
if (CurrentState == EAutomationState::InProcess)
{
OutCompletionState.NumEnabledInProcess++;
}
}
//Warnings
if (Results[ClusterIndex][PassIndex].Warnings.Num() > 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::NotEnoughParticipants )
{
IsEnabled() ? OutCompletionState.NumEnabledTestsCouldntBeRun++ : OutCompletionState.NumDisabledTestsCouldntBeRun++;
}
}
//recurse to children
for (int32 ChildIndex = 0; ChildIndex < ChildReports.Num(); ++ChildIndex)
{
ChildReports[ChildIndex]->GetCompletionStatus(ClusterIndex,PassIndex, OutCompletionState);
}
}
EAutomationState::Type 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;
}
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<IAutomationReport> FAutomationReport::EnsureReportExists(FAutomationTestInfo& InTestInfo, const int32 ClusterIndex, const int32 NumPasses)
{
//Split New Test Name by the first "." found
FString NameToMatch = InTestInfo.GetDisplayName();
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 );
}
uint32 NameToMatchHash = GetTypeHash(NameToMatch);
TSharedPtr<IAutomationReport> 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]->GetDisplayName() == NameToMatch)
{
MatchTest = ChildReports[TestIndex];
break;
}
}
}
//if there isn't already a test like this
if (!MatchTest.IsValid())
{
if ( NameRemainder.Len() == 0 )
{
// Create a new leaf node
MatchTest = MakeShareable(new FAutomationReport(InTestInfo));
}
else
{
// Create a parent node
FAutomationTestInfo ParentTestInfo( NameToMatch, "", InTestInfo.GetTestFlags(), InTestInfo.GetNumParticipantsRequired() ) ;
MatchTest = MakeShareable(new FAutomationReport(ParentTestInfo, true));
}
//make new test
ChildReports.Add(MatchTest);
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<IAutomationReport> 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<IAutomationReport> FAutomationReport::GetNextReportToExecute(bool& bOutAllTestsComplete, const int32 ClusterIndex, const int32 PassIndex, const int32 NumDevicesInCluster)
{
TSharedPtr<IAutomationReport> 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::Type TestState = GetState(ClusterIndex,PassIndex);
//if any enabled test hasn't been run yet or is in process
if ((TestState != EAutomationState::Success) && (TestState != EAutomationState::Fail) && (TestState != EAutomationState::NotEnoughParticipants))
{
//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;
}
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 ].Errors.Num() )
{
//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 ].Warnings.Num() )
{
//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();
}
}