Files
UnrealEngineUWP/Engine/Source/Developer/AutomationController/Private/AutomationControllerManger.cpp
Matt Kuhlenschmidt 47a097348a Copying //UE4/Dev-Editor to //UE4/Dev-Main (Source: //UE4/Dev-Editor @ 3167359)
#lockdown Nick.Penwarden
#rb none

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

Change 3152124 on 2016/10/05 by Jamie.Dale

	Fixed SOutputLog filter not handling OnTextCommitted

Change 3152255 on 2016/10/05 by Michael.Dupuis

	#jira UE-28173 Support \" properly in FName

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

	Core - The module manager is now thread safer, we had a critical section around the internal module list - but we were incrementing/decrementing shared pointers to module data shared pointers that were not thread safe outside of the critical section.  Ran into a crash working on some heavily threaded code in automation.

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

	Automation - Continued work to rough out the automation workflow for screenshot.  Still lots of work remaining, but it appears the basic of approving images might be working as of this CL.

Change 3152316 on 2016/10/05 by Michael.Dupuis

	#jira UE-30346 Update selection when in tree view mode

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

	Automation - Adding some test shots to compare against to EngineTest for screenshot approval.

Change 3152319 on 2016/10/05 by Michael.Dupuis

	#jira UE-29817 StringAssetReference will now only open an Asset picker (not actor picker) as the goal is to reference an asset

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

	Automation - Fixing some issues with where it reads the screenshot compare rules.

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

	Fix FBX automation test.
	- Make sure the fbx test can avoid automatic detection of the mesh type
	- Avoid to log the warning when the importer set the material usage after creating a material for skeletalmesh.

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

	Automation - The GameProjectAutomationTests now do some pre-run house cleaning to make sure the project doesn't already exist, and tries to remove it if it was created previously but not deleted.

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

	Automation - Changing the game project errors to be errors.

Change 3153115 on 2016/10/06 by Jamie.Dale

	Removed superflous padding when SPropertyEditorAsset had no buttons

Change 3153215 on 2016/10/06 by Michael.Dupuis

	Fixed build warning

Change 3153248 on 2016/10/06 by Nick.Darnell

	Automation - Working on solving projects not being generated, suspect UBT isn't built or isn't available.

Change 3153255 on 2016/10/06 by Nick.Darnell

	PR #2835: Fix TestEqual AddError Message in FAutomationTestBase (Contributed by dorgonman)

	#jira UE-36922

Change 3153300 on 2016/10/06 by Nick.Darnell

	Automation - Enabled verbose logging to automation build farm.

Change 3153343 on 2016/10/06 by Matt.Kuhlenschmidt

	PR #2825: More project launcher progress improvements (Contributed by projectgheist)

Change 3153506 on 2016/10/06 by Gareth.Martin

	Fixed crash trying to edit landscape with r.LightPropagationVolume=1 enabled
	#jira UE-36933

Change 3153752 on 2016/10/06 by tim.gautier

	Add toggle button to UMG_Behavior. Set Level Blueprint for TM-UMG to AllWidget

Change 3153763 on 2016/10/06 by Nick.Darnell

	Automation - Disable verbose logging.

Change 3153778 on 2016/10/06 by Nick.Darnell

	PR #2837: Fixed engine compilation with defined LOG_SLATE_EVENTS (Contributed by Pierdek)

	#jira UE-36940

Change 3153943 on 2016/10/06 by Nick.Darnell

	Automation - Disabling some broken tests.

Change 3154035 on 2016/10/06 by Nick.Darnell

	Automation - Fixing re-runs for tests that want them.  Previously this wasn't working for any test that was run using the Reprostring method of being executed.

Change 3154039 on 2016/10/06 by Nick.Darnell

	Automation - Updating some test assets in the EngineTest project.

Change 3154476 on 2016/10/07 by Richard.TalbotWatkin

	Fix to regression where vertex color view in Mesh Paint Mode no longer worked correctly. We now allow selected static meshes to go down the dynamic path if VertexColors show mode is active.
	#jira UE-36772 - Selecting a channel in Vertex Paint mode does not show the channel color

Change 3154650 on 2016/10/07 by Alexis.Matte

	Add new front axis facing X option to fbx importer

Change 3154785 on 2016/10/07 by Nick.Darnell

	Automation - Continued work on automation, ripping out the message bus from the screenshot manager, that's causing the screenshot manager to recieve shots from every machine ever running tests.  The Automation Controller is now in charge, and will tell the screenshot manager whatever it needs.

Change 3155131 on 2016/10/07 by Michael.Dupuis

	#jira UE-36509 Do not disabled inverse filter when doing a sync to asset

Change 3155141 on 2016/10/07 by Michael.Dupuis

	#jira UE-36056 Do not open the Actor Picker if we're working on an archetype object

Change 3155262 on 2016/10/07 by Michael.Dupuis

	#jira UE-19737 reset ctrl key when resetting state to None

Change 3156326 on 2016/10/09 by Matt.Kuhlenschmidt

	Fixed crash when asset picker is used without a property editor (usually a heavily customized property).

Change 3156473 on 2016/10/10 by Richard.TalbotWatkin

	Attempt to make geometry render / rebuild more robust in the hope of catching UE-36265.
	#jira UE-36265 - [CrashReport] UE4Editor_Engine!FModelSceneProxy::HasSelectedSurfaces() [modelrender.cpp:538]

Change 3156479 on 2016/10/10 by Richard.TalbotWatkin

	Fixed non-editor build.

Change 3156579 on 2016/10/10 by Alexis.Matte

	Add a check to make sure curve pointer is valid.
	#jira UE-36177

Change 3156585 on 2016/10/10 by Ben.Marsh

	Fix line endings for screenshot settings.

Change 3156617 on 2016/10/10 by Matt.Kuhlenschmidt

	Disable per-pixel blending of menus by default.  Causes artifacts on windows versions and we are not using it.

Change 3156674 on 2016/10/10 by Nick.Darnell

	Automation - Continued work on the automation workflow.  Now screenshot functional test actors actually have the screenshot processed for differences during the test back on the test controller machine, and then waits for the results of the comparison to be performed.

Change 3156709 on 2016/10/10 by Alexis.Matte

	#jira UE-16337
	Make sure the base mesh import data transform is used when we import a LOD.

Change 3156714 on 2016/10/10 by Nick.Darnell

	Automation - Fixing -game crash due to TestName being null in functional test.

Change 3156721 on 2016/10/10 by Nick.Darnell

	Automation - FunctionalAITest now implements IsReady to check if the navigation mesh has finished being built.

Change 3156748 on 2016/10/10 by Nick.Darnell

	Autopmation - Fixing a warning.

Change 3156943 on 2016/10/10 by Alex.Delesky

	Fixed an issue where closing out the "Add Code" window with an active toast stating that an IDE was downloading would prevent the toast from clearing correctly.

	#jira none

Change 3156946 on 2016/10/10 by Alex.Delesky

	#jira UE-36414 - Adding support for the P4Changelist command line argument to the P4 operations that were missing it.

Change 3158215 on 2016/10/11 by Nick.Darnell

	Automation - Continued work on the screenshot comparison, fixed a crash, the system now reports if the screenshot was new, as well as similar, so that the script can react and warn when new screenshots are produced.  Manually fired screenshots now properly wait until they've been compared before the test moves forward.

Change 3158322 on 2016/10/11 by Michael.Dupuis

	#jira UE-36063 If the collection is not shown when trying to save one, force show them so the user understand what is going on

Change 3158333 on 2016/10/11 by Alex.Delesky

	#jira UE-36829 - Rebuilt SVN 1.9.4 libs for Windows with CyrusSASL 2.1.26 and SWIG 3.0.10 support.

Change 3158399 on 2016/10/11 by Nick.Darnell

	Automation - TTF Font log statements that were not warnings are no longer warnings.

Change 3158406 on 2016/10/11 by Nick.Darnell

	Automation - Updating some automation scripts in the engnine that were using file paths to now use FStringAssetReferences, that cleaned up a lot of nasty convert filepath to asset reference code in the tests.  Also introducing some maps, and setting up the configs to use them to try and appease some of the existing tests that were expecting them.

Change 3158419 on 2016/10/11 by Alex.Delesky

	#jira UE-36829 - SVN 1.9.4 libraries, but also with Java 8u101 support.

Change 3158537 on 2016/10/11 by Nick.Darnell

	Automation - Updating some automation scripts in the engnine that were using file paths to now use FStringAssetReferences, that cleaned up a lot of nasty convert filepath to asset reference code in the tests.  Also introducing some maps, and setting up the configs to use them to try and appease some of the existing tests that were expecting them.

	Adding some missing files.

Change 3158726 on 2016/10/11 by Michael.Dupuis

	#jira UE-37001 Perform manual migration of UICurve to proper config category

Change 3158728 on 2016/10/11 by Nick.Darnell

	Automation - Fixing some warnings.  Adding more testing to the Domino map to serve as a better example.

Change 3158753 on 2016/10/11 by Michael.Dupuis

	#jira UE-26261 change it's by its

Change 3158984 on 2016/10/11 by Alexis.Matte

	Fix D&D folder import in content browser. We have to expand the root directory to have the correct path.
	#jira UE-32155

Change 3159640 on 2016/10/12 by Jamie.Dale

	Split localized package redirection out of FCoreDelegates::PackageNameResolvers

	They're different enough in behavior that the delegate resolution was breaking the localized package resolution by resolving in too many places and causing the localized package to be loaded with its real localized name as well as the fake non-localized name.

	#jira UE-37119

Change 3159741 on 2016/10/12 by Nick.Darnell

	Slate - Fixing the SGraphPanel's mouse offset calculations so that it works with HDPI mode.

Change 3159762 on 2016/10/12 by Nick.Darnell

	Automation - Adding a way to take a screenshot with the UI, not great yet - it doesn't really obey the rules of resolution, because of the method it uses.

Change 3160210 on 2016/10/12 by Gareth.Martin

	Fixed crash when changing Landscape LOD while using "Grass use Landscape Lightmap"

Change 3160216 on 2016/10/12 by Gareth.Martin

	Removed unused FLandscapeComponentSceneProxy::LayerNames and made LayerColors editor-only
	Fixed negative LODBias on landscape components to actually do anything

Change 3160239 on 2016/10/12 by Gareth.Martin

	Removed an unused variable

Change 3160455 on 2016/10/12 by Jamie.Dale

	Fixed FText properties exported to asset tags displaying in their NSLOCTEXT form in the asset tooltips

Change 3160457 on 2016/10/12 by Jamie.Dale

	Localization automation now groups everything into a single CL and reverts PO files without significant changes

Change 3160554 on 2016/10/12 by Nick.Darnell

	UMG - Fixing some panning logic to work with HDPI mode in the designer.

Change 3161712 on 2016/10/13 by Jamie.Dale

	Fixed TSharedMapView using hard-coded types

Change 3163044 on 2016/10/14 by Jamie.Dale

	Fixed line-break iterators incorrectly breaking words in CJK

Change 3163046 on 2016/10/14 by Jamie.Dale

	Text layout no longer creates break candidates when wrapping is disabled

Change 3163217 on 2016/10/14 by Jamie.Dale

	Fixed ensure caused by FMediaTextureResource::GetResourceSizeEx

Change 3163641 on 2016/10/14 by Alex.Delesky

	#jira UE-36829 - Updated Mac SVN libraries, along with a more accurate changelog for Windows SVN libs

Change 3164428 on 2016/10/17 by Nick.Darnell

	Slate - Making the FReply for SetMousePos easier to debug, since that option is potentially very frustrating to debug when someone is changing it.

Change 3164833 on 2016/10/17 by Jamie.Dale

	Fixed ParseNumber consuming strings with multiple sequential dots as valid numbers, eg) "10..."

Change 3164868 on 2016/10/17 by Alexis.Matte

	Remove re-import material and LOD import material
	#jira UE-36640

Change 3164874 on 2016/10/17 by Alexis.Matte

	Fix fbx scene re-import of staticmesh loosing there materials
	#jira UE-37032

Change 3165080 on 2016/10/17 by Alexis.Matte

	Remove skinxx workflow for static mesh
	#jira UE-37262

Change 3165232 on 2016/10/17 by Nick.Darnell

	Automation - Adding some sub-level testing.

Change 3165822 on 2016/10/18 by Nick.Darnell

	Slate - Add a counter to track how much time we spend drawing custom verts each frame.

Change 3165934 on 2016/10/18 by Nick.Darnell

	Slate - Addding cycle counters to SInvalidationPanel's Tick and Paint.

Change 3165947 on 2016/10/18 by Nick.Darnell

	Slate - Addding very verbose slate stats behind a compiler flag to avoid the large overhead of these stats.  To enable them you'll need to set WITH_VERY_VERBOSE_SLATE_STATS to 1, added a guide on debugging slate performance to the SlateGlobals.h

	// HOW TO GET AN IN-DEPTH PERFORMANCE ANALYSIS OF SLATE
	//
	// Step 1)
	//    Set WITH_VERY_VERBOSE_SLATE_STATS to 1.
	//
	// Step 2)
	//    When running the game (outside of the editor), run these commandline options
	//    in order and you'll get a large dump of where all the time is going in Slate.
	//
	//    stat group enable slateverbose
	//    stat group enable slateveryverbose
	//    stat dumpave -root=stat_slate -num=120 -ms=0

Change 3165962 on 2016/10/18 by Nick.Darnell

	UMG - Play first frame of sequence in UMG immediately when told to play an animation.

Change 3165981 on 2016/10/18 by Nick.Darnell

	Core - GetDisplayNameText now stores the FName for DisplayName in a static instead of using TEXT("DisplayName").

Change 3166000 on 2016/10/18 by Jamie.Dale

	Removed bulk-data from fonts

	The main complaints about composite fonts have always been:
	 1) They use too much memory at runtime.
	 2) They bloat if you use the same font face twice.
	 3) They often break when used outside the game thread.

	This change aims to address all of these issues by removing bulk-data from fonts (which was the cause of the memory bloat and threading issues), and introduces UFontFace assets (which allow you to re-use the same font face in different entries within a composite font).

	No existing font data is lost during this transition, however you must manually update your UFont assets to reference external UFontFace assets before you're able to edit the existing data (which is automatically upgraded to UFontFace assets *within* the UFont package). This upgrade can be done via the composite font editor.

	During cook these UFontFace assets write out the actual TTF/OTF font data as a .ufont file, which is what FreeType actually loads at runtime (the internals of the Slate font cache are now completely independent of UObjects and their lifetimes and loading concerns).

	Since we're now always loading files from disk, we can also give the user an option of whether to "pre-load" (which will load the entire font into memory, like bulk-data always used to), or "stream" the font from disk (which has minimal memory overhead, but needs decent I/O performance).

Change 3166001 on 2016/10/18 by Jamie.Dale

	Updated the Launcher to no longer use bulk-data for fonts

Change 3166003 on 2016/10/18 by Jamie.Dale

	Updated the Engine fonts to use UFontFace assets

Change 3166028 on 2016/10/18 by Alex.Delesky

	#jira UE-37021 - The scrollbar will now remain at the bottom of the output log after specifying a filter.

Change 3166071 on 2016/10/18 by Nick.Darnell

	Slate - Fixing a warning about hiding an inherited member.

Change 3166213 on 2016/10/18 by Jamie.Dale

	Fixing crash caused by accessing a zeroed FText

Change 3166222 on 2016/10/18 by Nick.Darnell

	Automation - Adding some code to end the sub level test when it starts.

Change 3166231 on 2016/10/18 by Nick.Darnell

	Editor - Fixing the build warning, SOutputLog.cpp:748:4: warning: field 'TextLayout' will be initialized after field 'CachedNumMessages'

Change 3166717 on 2016/10/18 by Nick.Darnell

	Automation - Removing references to old options that are no longer file paths, and are now StringAssetReferences these options were not being used by anyone as far as I can tell.

	#jira UE-37482

Change 3167279 on 2016/10/19 by Jamie.Dale

	Fixed text render component regression with custom MIDs

	#jira UE-37305

Change 3167356 on 2016/10/19 by Alexis.Matte

	Make sure the old asset are build correctly
	#jira UE-37461

Change 3167359 on 2016/10/19 by Alexis.Matte

	Fix re-import of mesh material assignment regression
	#jira UE-37479

[CL 3168049 by Matt Kuhlenschmidt in Main branch]
2016-10-19 15:01:48 -04:00

938 lines
31 KiB
C++

// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved.
#include "AutomationControllerPrivatePCH.h"
#include "JsonObjectConverter.h"
#if WITH_EDITOR
#include "MessageLog.h"
#endif
namespace AutomationControllerConstants
{
const FString HistoryConfigSectionName = TEXT("AutomationController.History");
}
void FAutomationControllerManager::RequestAvailableWorkers(const FGuid& SessionId)
{
//invalidate previous tests
++ExecutionCount;
DeviceClusterManager.Reset();
ControllerResetDelegate.Broadcast();
// Don't allow reports to be exported
bTestResultsAvailable = false;
//store off active session ID to reject messages that come in from different sessions
ActiveSessionId = SessionId;
//TODO AUTOMATION - include change list, game, etc, or remove when launcher is integrated
int32 ChangelistNumber = 10000;
FString ProcessName = TEXT("instance_name");
MessageEndpoint->Publish(new FAutomationWorkerFindWorkers(ChangelistNumber, FApp::GetGameName(), ProcessName, SessionId), EMessageScope::Network);
// Reset the check test timers
LastTimeUpdateTicked = FPlatformTime::Seconds();
CheckTestTimer = 0.f;
IScreenShotToolsModule& ScreenShotModule = FModuleManager::LoadModuleChecked<IScreenShotToolsModule>("ScreenShotComparisonTools");
ScreenshotManager = ScreenShotModule.GetScreenShotManager();
}
void FAutomationControllerManager::RequestTests()
{
//invalidate incoming results
ExecutionCount++;
//reset the number of responses we have received
RefreshTestResponses = 0;
ReportManager.Empty();
for (int32 ClusterIndex = 0; ClusterIndex < DeviceClusterManager.GetNumClusters(); ++ClusterIndex)
{
int32 DevicesInCluster = DeviceClusterManager.GetNumDevicesInCluster(ClusterIndex);
if (DevicesInCluster > 0)
{
FMessageAddress MessageAddress = DeviceClusterManager.GetDeviceMessageAddress(ClusterIndex, 0);
ResetIntermediateTestData();
//issue tests on appropriate platforms
MessageEndpoint->Send(new FAutomationWorkerRequestTests(bDeveloperDirectoryIncluded, RequestedTestFlags), MessageAddress);
}
}
}
void FAutomationControllerManager::RunTests( const bool bInIsLocalSession )
{
ExecutionCount++;
CurrentTestPass = 0;
ReportManager.SetCurrentTestPass(CurrentTestPass);
ClusterDistributionMask = 0;
bTestResultsAvailable = false;
TestRunningArray.Empty();
bIsLocalSession = bInIsLocalSession;
// Reset the check test timers
LastTimeUpdateTicked = FPlatformTime::Seconds();
CheckTestTimer = 0.f;
#if WITH_EDITOR
FMessageLog AutomationTestingLog("AutomationTestingLog");
FString NewPageName = FString::Printf(TEXT("-----Test Run %d----"), ExecutionCount);
FText NewPageNameText = FText::FromString(*NewPageName);
AutomationTestingLog.Open();
AutomationTestingLog.NewPage(NewPageNameText);
AutomationTestingLog.Info(NewPageNameText);
#endif
//reset all tests
ReportManager.ResetForExecution(NumTestPasses);
for (int32 ClusterIndex = 0; ClusterIndex < DeviceClusterManager.GetNumClusters(); ++ClusterIndex)
{
//enable each device cluster
ClusterDistributionMask |= (1<<ClusterIndex);
//for each device in this cluster
for (int32 DeviceIndex = 0; DeviceIndex < DeviceClusterManager.GetNumDevicesInCluster(ClusterIndex); ++DeviceIndex)
{
//mark the device as idle
DeviceClusterManager.SetTest(ClusterIndex, DeviceIndex, NULL);
// Send command to reset tests (delete local files, etc)
FMessageAddress MessageAddress = DeviceClusterManager.GetDeviceMessageAddress(ClusterIndex, DeviceIndex);
MessageEndpoint->Send(new FAutomationWorkerResetTests(), MessageAddress);
}
}
// Inform the UI we are running tests
if (ClusterDistributionMask != 0)
{
SetControllerStatus( EAutomationControllerModuleState::Running );
}
}
void FAutomationControllerManager::StopTests()
{
bTestResultsAvailable = false;
ClusterDistributionMask = 0;
ReportManager.StopRunningTests();
// Inform the UI we have stopped running tests
if (DeviceClusterManager.HasActiveDevice())
{
SetControllerStatus( EAutomationControllerModuleState::Ready );
}
else
{
SetControllerStatus( EAutomationControllerModuleState::Disabled );
}
TestRunningArray.Empty();
}
void FAutomationControllerManager::Init()
{
AutomationTestState = EAutomationControllerModuleState::Disabled;
bTestResultsAvailable = false;
bScreenshotsEnabled = true;
bSendAnalytics = FParse::Param(FCommandLine::Get(), TEXT("SendAutomationAnalytics"));
// Update the ini with the settings
bTrackHistory = false;
GConfig->GetBool(*AutomationControllerConstants::HistoryConfigSectionName, TEXT("bTrackHistory"), bTrackHistory, GEngineIni);
// Default num of items to track
NumberOfHistoryItemsTracked = 5;
GConfig->GetInt(*AutomationControllerConstants::HistoryConfigSectionName, TEXT("NumberOfHistoryItemsTracked"), NumberOfHistoryItemsTracked, GEngineIni);
}
void FAutomationControllerManager::RequestLoadAsset( const FString& InAssetName )
{
MessageEndpoint->Publish(new FAssetEditorRequestOpenAsset(InAssetName), EMessageScope::Process);
}
void FAutomationControllerManager::Tick()
{
ProcessAvailableTasks();
ProcessComparisonQueue();
}
void FAutomationControllerManager::ProcessComparisonQueue()
{
TSharedPtr<FComparisonEntry> Entry;
if ( ComparisonQueue.Peek(Entry) )
{
if ( Entry->PendingComparison.IsReady() )
{
const bool Dequeued = ComparisonQueue.Dequeue(Entry);
check(Dequeued);
FImageComparisonResult Result = Entry->PendingComparison.Get();
const bool bIsNew = Result.IsNew();
const bool bAreSimilar = Result.AreSimilar();
// Issue tests on appropriate platforms
MessageEndpoint->Send(new FAutomationWorkerImageComparisonResults(bIsNew, bAreSimilar), Entry->Sender);
}
}
}
void FAutomationControllerManager::ProcessAvailableTasks()
{
// Distribute tasks
if( ClusterDistributionMask != 0)
{
// For each device cluster
for( int32 ClusterIndex = 0; ClusterIndex < DeviceClusterManager.GetNumClusters(); ++ClusterIndex )
{
bool bAllTestsComplete = true;
// If any of the devices were valid
if( ( ClusterDistributionMask & ( 1<< ClusterIndex ) ) && DeviceClusterManager.GetNumDevicesInCluster( ClusterIndex ) > 0 )
{
ExecuteNextTask( ClusterIndex, bAllTestsComplete );
}
//if we're all done running our tests
if ( bAllTestsComplete )
{
//we don't need to test this cluster anymore
ClusterDistributionMask &= ~( 1<<ClusterIndex );
if ( ClusterDistributionMask == 0 )
{
ProcessResults();
//Notify the graphical layout we are done processing results.
TestsCompleteDelegate.Broadcast();
}
}
}
}
if (bIsLocalSession == false)
{
// Update the test status for timeouts if this is not a local session
UpdateTests();
}
}
void FAutomationControllerManager::ReportTestResults()
{
GLog->Logf(TEXT("Test Pass Results:"));
for (int i = 0; i < OurPassResults.TestInformation.Num(); i++)
{
GLog->Logf(TEXT("%s: %s"), *OurPassResults.TestInformation[i].TestName, *OurPassResults.TestInformation[i].TestResult);
}
}
void FAutomationControllerManager::CollectTestNotes(FString TestName, const FAutomationWorkerRunTestsReply& Message)
{
for (int i = 0; i < OurPassResults.TestInformation.Num(); i++)
{
if (OurPassResults.TestInformation[i].TestName == TestName)
{
OurPassResults.TestInformation[i].TestInfo = Message.Logs;
OurPassResults.TestInformation[i].TestWarnings = Message.Warnings;
for (int j = 0; j < Message.Errors.Num(); j++)
{
OurPassResults.TestInformation[i].TestErrors.Add(Message.Errors[j].Message);
}
return;
}
}
}
void FAutomationControllerManager::UpdateTestResultValue(FString TestName, EAutomationState::Type TestResult)
{
for (int i = 0; i < OurPassResults.TestInformation.Num(); i++)
{
if (OurPassResults.TestInformation[i].TestName == TestName)
{
OurPassResults.TestInformation[i].TestResult = EAutomationState::ToString(TestResult);
switch (TestResult)
{
case EAutomationState::Success:
OurPassResults.NumSucceeded++;
break;
case EAutomationState::Fail:
OurPassResults.NumFailed++;
break;
default:
OurPassResults.NumNotRun++;
break;
}
return;
}
}
}
void FAutomationControllerManager::GenerateJsonTestPassSummary()
{
if (!OurPassResults.TestInformation.Num())
{
return;
}
const FAutomatedTestPassResults SerializedPassResults = OurPassResults;
TSharedPtr<FJsonObject> ReportJson = FJsonObjectConverter::UStructToJsonObject(SerializedPassResults);
if (ReportJson.IsValid())
{
FString ReportFileName = FString::Printf(TEXT("%s/AutomationReport-%d-%s.json"), *FPaths::AutomationLogDir(), FEngineVersion::Current().GetChangelist(), *FDateTime::Now().ToString());
FArchive* ReportFileWriter = IFileManager::Get().CreateFileWriter(*ReportFileName);
if (ReportFileWriter != nullptr)
{
TSharedRef<TJsonWriter<> > JsonWriter = TJsonWriterFactory<>::Create(ReportFileWriter, 0);
FJsonSerializer::Serialize(ReportJson.ToSharedRef(), JsonWriter);
delete ReportFileWriter;
}
}
else
{
GLog->Logf(ELogVerbosity::Error, TEXT("Test Report Json is invalid - report not generated."));
}
}
void FAutomationControllerManager::ExecuteNextTask( int32 ClusterIndex, OUT bool& bAllTestsCompleted )
{
bool bTestThatRequiresMultiplePraticipantsHadEnoughParticipants = false;
TArray< IAutomationReportPtr > TestsRunThisPass;
// For each device in this cluster
int32 NumDevicesInCluster = DeviceClusterManager.GetNumDevicesInCluster( ClusterIndex );
for( int32 DeviceIndex = 0; DeviceIndex < NumDevicesInCluster; ++DeviceIndex )
{
// If this device is idle
if( !DeviceClusterManager.GetTest( ClusterIndex, DeviceIndex ).IsValid() && DeviceClusterManager.DeviceEnabled( ClusterIndex, DeviceIndex ) )
{
// Get the next test that should be worked on
TSharedPtr< IAutomationReport > NextTest = ReportManager.GetNextReportToExecute( bAllTestsCompleted, ClusterIndex,CurrentTestPass, NumDevicesInCluster );
if( NextTest.IsValid() )
{
// Get the status of the test
EAutomationState::Type TestState = NextTest->GetState( ClusterIndex,CurrentTestPass );
if( TestState == EAutomationState::NotRun )
{
// Reserve this device for the test
DeviceClusterManager.SetTest( ClusterIndex, DeviceIndex, NextTest );
TestsRunThisPass.Add( NextTest );
// Register this as a test we'll need to report on.
FAutomatedTestResult tempresult;
tempresult.TestName = NextTest->GetDisplayName();
OurPassResults.TestInformation.Add(tempresult);
// If we now have enough devices reserved for the test, run it!
TArray<FMessageAddress> DeviceAddresses = DeviceClusterManager.GetDevicesReservedForTest( ClusterIndex, NextTest );
if( DeviceAddresses.Num() == NextTest->GetNumParticipantsRequired() )
{
// Send it to each device
for (int32 AddressIndex = 0; AddressIndex < DeviceAddresses.Num(); ++AddressIndex)
{
FAutomationTestResults TestResults;
GLog->Logf(ELogVerbosity::Display, TEXT("Running Automation: '%s' (Class Name: '%s')"), *TestsRunThisPass[AddressIndex]->GetFullTestPath(), *TestsRunThisPass[AddressIndex]->GetCommand());
TestResults.State = EAutomationState::InProcess;
TestResults.GameInstance = DeviceClusterManager.GetClusterDeviceName( ClusterIndex, DeviceIndex );
NextTest->SetResults( ClusterIndex,CurrentTestPass, TestResults );
NextTest->ResetNetworkCommandResponses();
// Mark the device as busy
FMessageAddress DeviceAddress = DeviceAddresses[AddressIndex];
// Send the test to the device for execution!
MessageEndpoint->Send(new FAutomationWorkerRunTests(ExecutionCount, AddressIndex, NextTest->GetCommand(), NextTest->GetDisplayName(), bScreenshotsEnabled, bSendAnalytics), DeviceAddress);
// Add a test so we can check later if the device is still active
TestRunningArray.Add( FTestRunningInfo( DeviceAddress ) );
}
}
}
}
}
else
{
// At least one device is still working
bAllTestsCompleted = false;
}
}
// Ensure any tests we have attempted to run on this pass had enough participants to successfully run.
for( int32 TestIndex = 0; TestIndex < TestsRunThisPass.Num(); TestIndex++ )
{
IAutomationReportPtr CurrentTest = TestsRunThisPass[ TestIndex ];
if( CurrentTest->GetNumDevicesRunningTest() != CurrentTest->GetNumParticipantsRequired() )
{
if( GetNumDevicesInCluster( ClusterIndex ) < CurrentTest->GetNumParticipantsRequired() )
{
float EmptyDuration = 0.0f;
TArray<FString> EmptyStringArray;
TArray<FString> AutomationsWarnings;
AutomationsWarnings.Add( FString::Printf( TEXT( "Needed %d devices to participate, Only had %d available." ), CurrentTest->GetNumParticipantsRequired(), DeviceClusterManager.GetNumDevicesInCluster( ClusterIndex ) ) );
FAutomationTestResults TestResults;
TestResults.State = EAutomationState::NotEnoughParticipants;
TestResults.GameInstance = DeviceClusterManager.GetClusterDeviceName(ClusterIndex, 0);
TestResults.Warnings.Append( AutomationsWarnings );
CurrentTest->SetResults( ClusterIndex,CurrentTestPass, TestResults );
DeviceClusterManager.ResetAllDevicesRunningTest( ClusterIndex, CurrentTest );
}
}
}
//Check to see if we finished a pass
if( bAllTestsCompleted && CurrentTestPass < NumTestPasses - 1 )
{
CurrentTestPass++;
ReportManager.SetCurrentTestPass(CurrentTestPass);
bAllTestsCompleted = false;
}
}
void FAutomationControllerManager::Startup()
{
MessageEndpoint = FMessageEndpoint::Builder("FAutomationControllerModule")
.Handling<FAutomationWorkerFindWorkersResponse>(this, &FAutomationControllerManager::HandleFindWorkersResponseMessage)
.Handling<FAutomationWorkerPong>(this, &FAutomationControllerManager::HandlePongMessage)
.Handling<FAutomationWorkerRequestNextNetworkCommand>(this, &FAutomationControllerManager::HandleRequestNextNetworkCommandMessage)
.Handling<FAutomationWorkerRequestTestsReply>(this, &FAutomationControllerManager::HandleRequestTestsReplyMessage)
.Handling<FAutomationWorkerRequestTestsReplyComplete>(this, &FAutomationControllerManager::HandleRequestTestsReplyCompleteMessage)
.Handling<FAutomationWorkerRunTestsReply>(this, &FAutomationControllerManager::HandleRunTestsReplyMessage)
.Handling<FAutomationWorkerScreenImage>(this, &FAutomationControllerManager::HandleReceivedScreenShot)
.Handling<FAutomationWorkerWorkerOffline>(this, &FAutomationControllerManager::HandleWorkerOfflineMessage);
if (MessageEndpoint.IsValid())
{
MessageEndpoint->Subscribe<FAutomationWorkerWorkerOffline>();
}
ClusterDistributionMask = 0;
ExecutionCount = 0;
bDeveloperDirectoryIncluded = false;
RequestedTestFlags = EAutomationTestFlags::SmokeFilter | EAutomationTestFlags::EngineFilter | EAutomationTestFlags::ProductFilter | EAutomationTestFlags::PerfFilter;
NumOfTestsToReceive = 0;
NumTestPasses = 1;
//Default to machine name
DeviceGroupFlags = 0;
ToggleDeviceGroupFlag(EAutomationDeviceGroupTypes::MachineName);
}
void FAutomationControllerManager::Shutdown()
{
MessageEndpoint.Reset();
ShutdownDelegate.Broadcast();
RemoveCallbacks();
}
void FAutomationControllerManager::RemoveCallbacks()
{
ShutdownDelegate.Clear();
TestsAvailableDelegate.Clear();
TestsRefreshedDelegate.Clear();
TestsCompleteDelegate.Clear();
}
void FAutomationControllerManager::SetTestNames( const FMessageAddress& AutomationWorkerAddress )
{
int32 DeviceClusterIndex = INDEX_NONE;
int32 DeviceIndex = INDEX_NONE;
// Find the device that requested these tests
if( DeviceClusterManager.FindDevice( AutomationWorkerAddress, DeviceClusterIndex, DeviceIndex ) )
{
// Sort tests by display name
struct FCompareAutomationTestInfo
{
FORCEINLINE bool operator()(const FAutomationTestInfo& A,const FAutomationTestInfo& B) const
{
return A.GetDisplayName() < B.GetDisplayName();
}
};
TestInfo.Sort(FCompareAutomationTestInfo());
// Add each test to the collection
for( int32 TestIndex = 0; TestIndex < TestInfo.Num(); ++TestIndex )
{
// Ensure our test exists. If not, add it
ReportManager.EnsureReportExists( TestInfo[TestIndex], DeviceClusterIndex, NumTestPasses );
}
// Clear any intermediate data we had associated with the tests whilst building the full list of tests
ResetIntermediateTestData();
}
else
{
//todo automation - make sure to report error if the device was not discovered correctly
}
// Note the response
RefreshTestResponses++;
// If we have received all the responses we expect to
if (RefreshTestResponses == DeviceClusterManager.GetNumClusters())
{
TestsRefreshedDelegate.Broadcast();
// Update the tests with tracking details
ReportManager.TrackHistory(bTrackHistory, NumberOfHistoryItemsTracked);
}
}
void FAutomationControllerManager::ProcessResults()
{
bHasErrors = false;
bHasWarning = false;
bHasLogs = false;
TArray< TSharedPtr< IAutomationReport > >& TestReports = GetReports();
if (TestReports.Num())
{
bTestResultsAvailable = true;
for (int32 Index = 0; Index < TestReports.Num(); Index++)
{
CheckChildResult(TestReports[Index ]);
}
}
// Write our the report of our automation pass to JSON. Then clean our array for the next pass.
GenerateJsonTestPassSummary();
OurPassResults.ClearAllEntries();
SetControllerStatus( EAutomationControllerModuleState::Ready );
}
void FAutomationControllerManager::CheckChildResult(TSharedPtr<IAutomationReport> InReport)
{
TArray<TSharedPtr<IAutomationReport> >& ChildReports = InReport->GetChildReports();
if (ChildReports.Num() > 0)
{
for (int32 Index = 0; Index < ChildReports.Num(); Index++)
{
CheckChildResult(ChildReports[Index]);
}
}
else if ((bHasErrors && bHasWarning && bHasLogs ) == false && InReport->IsEnabled())
{
for (int32 ClusterIndex = 0; ClusterIndex < GetNumDeviceClusters(); ++ClusterIndex)
{
FAutomationTestResults TestResults = InReport->GetResults( ClusterIndex,CurrentTestPass );
if (TestResults.Errors.Num())
{
bHasErrors = true;
}
if (TestResults.Warnings.Num())
{
bHasWarning = true;
}
if (TestResults.Logs.Num())
{
bHasLogs = true;
}
}
}
}
void FAutomationControllerManager::SetControllerStatus( EAutomationControllerModuleState::Type InAutomationTestState )
{
if (InAutomationTestState != AutomationTestState)
{
// Inform the UI if the test state has changed
AutomationTestState = InAutomationTestState;
TestsAvailableDelegate.Broadcast(AutomationTestState);
}
}
void FAutomationControllerManager::RemoveTestRunning( const FMessageAddress& TestAddressToRemove )
{
for ( int32 Index = 0; Index < TestRunningArray.Num(); Index++ )
{
if ( TestRunningArray[ Index ].OwnerMessageAddress == TestAddressToRemove )
{
TestRunningArray.RemoveAt( Index );
break;
}
}
}
void FAutomationControllerManager::AddPingResult( const FMessageAddress& ResponderAddress )
{
for ( int32 Index = 0; Index < TestRunningArray.Num(); Index++ )
{
if ( TestRunningArray[ Index ].OwnerMessageAddress == ResponderAddress )
{
TestRunningArray[ Index ].LastPingTime = 0;
break;
}
}
}
void FAutomationControllerManager::UpdateTests( )
{
static const float CheckTestInterval = 1.0f;
static const float GameInstanceLostTimer = 200.0f;
CheckTestTimer += FPlatformTime::Seconds() - LastTimeUpdateTicked;
LastTimeUpdateTicked = FPlatformTime::Seconds();
if ( CheckTestTimer > CheckTestInterval )
{
for ( int32 Index = 0; Index < TestRunningArray.Num(); Index++ )
{
TestRunningArray[ Index ].LastPingTime += CheckTestTimer;
if ( TestRunningArray[ Index ].LastPingTime > GameInstanceLostTimer )
{
// Find the game session instance info
int32 ClusterIndex;
int32 DeviceIndex;
verify( DeviceClusterManager.FindDevice( TestRunningArray[ Index ].OwnerMessageAddress, ClusterIndex, DeviceIndex ) );
//verify this device thought it was busy
TSharedPtr <IAutomationReport> Report = DeviceClusterManager.GetTest(ClusterIndex, DeviceIndex);
check (Report.IsValid());
// A dummy array used to report the result
TArray<FString> EmptyStringArray;
TArray<FString> ErrorStringArray;
ErrorStringArray.Add( FString( TEXT( "Failed" ) ) );
bHasErrors = true;
GLog->Logf(ELogVerbosity::Display, TEXT("Timeout hit. Nooooooo."));
FAutomationTestResults TestResults;
TestResults.State = EAutomationState::Fail;
TestResults.GameInstance = DeviceClusterManager.GetClusterDeviceName(ClusterIndex, DeviceIndex);
// Set the results
Report->SetResults(ClusterIndex,CurrentTestPass, TestResults );
bTestResultsAvailable = true;
// Disable the device in the cluster so it is not used again
DeviceClusterManager.DisableDevice( ClusterIndex, DeviceIndex );
// Remove the running test
TestRunningArray.RemoveAt( Index-- );
// If there are no more devices, set the module state to disabled
if ( DeviceClusterManager.HasActiveDevice() == false )
{
GLog->Logf(ELogVerbosity::Display, TEXT("Module disabled"));
SetControllerStatus( EAutomationControllerModuleState::Disabled );
ClusterDistributionMask = 0;
}
else
{
GLog->Logf(ELogVerbosity::Display, TEXT("Module not disabled. Keep looking."));
// Remove the cluster from the mask if there are no active devices left
if ( DeviceClusterManager.GetNumActiveDevicesInCluster( ClusterIndex ) == 0 )
{
ClusterDistributionMask &= ~( 1<<ClusterIndex );
}
if ( TestRunningArray.Num() == 0 )
{
SetControllerStatus( EAutomationControllerModuleState::Ready );
}
}
}
else
{
MessageEndpoint->Send(new FAutomationWorkerPing(), TestRunningArray[Index].OwnerMessageAddress);
}
}
CheckTestTimer = 0.f;
}
}
const bool FAutomationControllerManager::ExportReport( uint32 FileExportTypeMask )
{
return ReportManager.ExportReport( FileExportTypeMask, GetNumDeviceClusters() );
}
bool FAutomationControllerManager::IsTestRunnable( IAutomationReportPtr InReport ) const
{
bool bIsRunnable = false;
for( int32 ClusterIndex = 0; ClusterIndex < GetNumDeviceClusters(); ++ClusterIndex )
{
if( InReport->IsSupported( ClusterIndex ) )
{
if( GetNumDevicesInCluster( ClusterIndex ) >= InReport->GetNumParticipantsRequired() )
{
bIsRunnable = true;
break;
}
}
}
return bIsRunnable;
}
/* FAutomationControllerModule callbacks
*****************************************************************************/
void FAutomationControllerManager::HandleFindWorkersResponseMessage( const FAutomationWorkerFindWorkersResponse& Message, const IMessageContextRef& Context )
{
if (Message.SessionId == ActiveSessionId)
{
DeviceClusterManager.AddDeviceFromMessage(Context->GetSender(), Message, DeviceGroupFlags);
}
RequestTests();
SetControllerStatus( EAutomationControllerModuleState::Ready );
}
void FAutomationControllerManager::HandlePongMessage( const FAutomationWorkerPong& Message, const IMessageContextRef& Context )
{
AddPingResult(Context->GetSender());
}
void FAutomationControllerManager::HandleReceivedScreenShot( const FAutomationWorkerScreenImage& Message, const IMessageContextRef& Context )
{
FString ScreenshotIncomingFolder = FPaths::GameSavedDir() / TEXT("Automation/Incoming/");
bool bTree = true;
FString FileName = ScreenshotIncomingFolder / Message.ScreenShotName;
IFileManager::Get().MakeDirectory(*FPaths::GetPath(FileName), bTree);
FFileHelper::SaveArrayToFile(Message.ScreenImage, *FileName);
// TODO Automation There is identical code in, Engine\Source\Runtime\AutomationWorker\Private\AutomationWorkerModule.cpp,
// need to move this code into common area.
FString Json;
if ( FJsonObjectConverter::UStructToJsonObjectString(Message.Metadata, Json) )
{
FString MetadataPath = FPaths::ChangeExtension(FileName, TEXT("json"));
FFileHelper::SaveStringToFile(Json, *MetadataPath, FFileHelper::EEncodingOptions::ForceUTF8);
}
TSharedRef<FComparisonEntry> Comparison = MakeShareable(new FComparisonEntry());
Comparison->Sender = Context->GetSender();
Comparison->PendingComparison = ScreenshotManager->CompareScreensotAsync(Message.ScreenShotName);
ComparisonQueue.Enqueue(Comparison);
}
void FAutomationControllerManager::HandleRequestNextNetworkCommandMessage( const FAutomationWorkerRequestNextNetworkCommand& Message, const IMessageContextRef& Context )
{
// Harvest iteration of running the tests this result came from (stops stale results from being committed to subsequent runs)
if (Message.ExecutionCount == ExecutionCount)
{
// Find the device id for the address
int32 ClusterIndex;
int32 DeviceIndex;
verify(DeviceClusterManager.FindDevice(Context->GetSender(), ClusterIndex, DeviceIndex));
// Verify this device thought it was busy
TSharedPtr <IAutomationReport> Report = DeviceClusterManager.GetTest(ClusterIndex, DeviceIndex);
check (Report.IsValid());
// Increment network command responses
bool bAllResponsesReceived = Report->IncrementNetworkCommandResponses();
// Test if we've accumulated all responses AND this was the result for the round of test running AND we're still running tests
if (bAllResponsesReceived && (ClusterDistributionMask & (1<<ClusterIndex)))
{
// Reset the counter
Report->ResetNetworkCommandResponses();
// For every device in this networked test
TArray<FMessageAddress> DeviceAddresses = DeviceClusterManager.GetDevicesReservedForTest(ClusterIndex, Report);
check (DeviceAddresses.Num() == Report->GetNumParticipantsRequired());
// Send it to each device
for (int32 AddressIndex = 0; AddressIndex < DeviceAddresses.Num(); ++AddressIndex)
{
//send "next command message" to worker
MessageEndpoint->Send(new FAutomationWorkerNextNetworkCommandReply(), DeviceAddresses[AddressIndex]);
}
}
}
}
void FAutomationControllerManager::HandleRequestTestsReplyMessage(const FAutomationWorkerRequestTestsReply& Message, const IMessageContextRef& Context)
{
FAutomationTestInfo NewTest = Message.GetTestInfo();
TestInfo.Add(NewTest);
}
void FAutomationControllerManager::HandleRequestTestsReplyCompleteMessage(const FAutomationWorkerRequestTestsReplyComplete& Message, const IMessageContextRef& Context)
{
SetTestNames(Context->GetSender());
}
void FAutomationControllerManager::HandleRunTestsReplyMessage( const FAutomationWorkerRunTestsReply& Message, const IMessageContextRef& Context )
{
// If we should commit these results
if (Message.ExecutionCount == ExecutionCount)
{
FAutomationTestResults TestResults;
TestResults.State = Message.Success ? EAutomationState::Success : EAutomationState::Fail;
TestResults.Duration = Message.Duration;
// Mark device as back on the market
int32 ClusterIndex;
int32 DeviceIndex;
verify(DeviceClusterManager.FindDevice(Context->GetSender(), ClusterIndex, DeviceIndex));
TestResults.GameInstance = DeviceClusterManager.GetClusterDeviceName(ClusterIndex, DeviceIndex);
for ( auto& Error : Message.Errors )
{
TestResults.Errors.Add(Error.ToAutomationEvent());
}
TestResults.Logs = Message.Logs;
TestResults.Warnings = Message.Warnings;
// Verify this device thought it was busy
TSharedPtr <IAutomationReport> Report = DeviceClusterManager.GetTest(ClusterIndex, DeviceIndex);
check(Report.IsValid());
Report->SetResults(ClusterIndex,CurrentTestPass, TestResults);
// Gather all of the data relevant to this test for our json reporting.
CollectTestNotes(Report->GetDisplayName(), Message);
UpdateTestResultValue(Report->GetDisplayName(), TestResults.State);
#if WITH_EDITOR
FMessageLog AutomationTestingLog("AutomationTestingLog");
AutomationTestingLog.Open();
#endif
for ( TArray<FAutomationEvent>::TConstIterator ErrorIter(TestResults.Errors); ErrorIter; ++ErrorIter )
{
// FAutomationTestFramework::Get().LogTestMessage(**ErrorIter, ELogVerbosity::Error);
GLog->Logf(ELogVerbosity::Error, TEXT("%s"), *( *ErrorIter ).ToString());
#if WITH_EDITOR
AutomationTestingLog.Error(FText::FromString(( *ErrorIter ).ToString()));
#endif
}
for (TArray<FString>::TConstIterator WarningIter(Message.Warnings); WarningIter; ++WarningIter)
{
GLog->Logf(ELogVerbosity::Warning, TEXT("%s"), **WarningIter);
#if WITH_EDITOR
AutomationTestingLog.Warning(FText::FromString(*WarningIter));
#endif
}
for (TArray<FString>::TConstIterator LogItemIter(Message.Logs); LogItemIter; ++LogItemIter)
{
GLog->Logf(ELogVerbosity::Log, TEXT("%s"), **LogItemIter);
#if WITH_EDITOR
AutomationTestingLog.Info(FText::FromString(*LogItemIter));
#endif
}
if (TestResults.State == EAutomationState::Success)
{
FString SuccessString = FString::Printf(TEXT("...Automation Test Succeeded (%s)"), *Report->GetDisplayName());
GLog->Logf(ELogVerbosity::Log, *SuccessString);
#if WITH_EDITOR
AutomationTestingLog.Info(FText::FromString(*SuccessString));
#endif
}
else
{
FString FailureString = FString::Printf(TEXT("...Automation Test Failed (%s)"), *Report->GetDisplayName());
GLog->Logf(ELogVerbosity::Log, *FailureString);
#if WITH_EDITOR
AutomationTestingLog.Error(FText::FromString(*FailureString));
#endif
//FAutomationTestFramework::Get().Lo
}
// const bool TestSucceeded = (TestResults.State == EAutomationState::Success);
//FAutomationTestFramework::Get().LogEndTestMessage(Report->GetDisplayName(), TestSucceeded);
// Device is now good to go
DeviceClusterManager.SetTest(ClusterIndex, DeviceIndex, NULL);
}
// Remove the running test
RemoveTestRunning(Context->GetSender());
}
void FAutomationControllerManager::HandleWorkerOfflineMessage( const FAutomationWorkerWorkerOffline& Message, const IMessageContextRef& Context )
{
FMessageAddress DeviceMessageAddress = Context->GetSender();
DeviceClusterManager.Remove(DeviceMessageAddress);
}
bool FAutomationControllerManager::IsDeviceGroupFlagSet( EAutomationDeviceGroupTypes::Type InDeviceGroup ) const
{
const uint32 FlagMask = 1 << InDeviceGroup;
return (DeviceGroupFlags & FlagMask) > 0;
}
void FAutomationControllerManager::ToggleDeviceGroupFlag( EAutomationDeviceGroupTypes::Type InDeviceGroup )
{
const uint32 FlagMask = 1 << InDeviceGroup;
DeviceGroupFlags = DeviceGroupFlags ^ FlagMask;
}
void FAutomationControllerManager::UpdateDeviceGroups( )
{
DeviceClusterManager.ReGroupDevices( DeviceGroupFlags );
// Update the reports in case the number of clusters changed
int32 NumOfClusters = DeviceClusterManager.GetNumClusters();
ReportManager.ClustersUpdated(NumOfClusters);
}
void FAutomationControllerManager::TrackReportHistory(const bool bShouldTrack, const int32 NumReportsToTrack)
{
bTrackHistory = bShouldTrack;
NumberOfHistoryItemsTracked = NumReportsToTrack;
// Update the ini with the settings
GConfig->SetBool(*AutomationControllerConstants::HistoryConfigSectionName, TEXT("bTrackHistory"), bTrackHistory, GEngineIni);
GConfig->SetInt(*AutomationControllerConstants::HistoryConfigSectionName, TEXT("NumberOfHistoryItemsTracked"), NumberOfHistoryItemsTracked, GEngineIni);
ReportManager.TrackHistory(bTrackHistory, NumberOfHistoryItemsTracked);
}
const bool FAutomationControllerManager::IsTrackingHistory() const
{
return bTrackHistory;
}
const int32 FAutomationControllerManager::GetNumberHistoryItemsTracking() const
{
return NumberOfHistoryItemsTracked;
}