// Copyright Epic Games, Inc. All Rights Reserved.
using AutomationTool;
using Gauntlet;
using System;
using System.Collections.Generic;
using System.Linq;
namespace UE
{
///
/// Configuration parameters for the Process Lifetime Management (PLM) tests
///
public class PLMTestConfiguration : UnrealTestConfiguration
{
/// How long to wait before starting after the engine has been initialized
[AutoParam]
public int InitialWait = 0;
/// How long to wait between loops
[AutoParam]
public int LoopWait = 5;
/// How many times to loop
[AutoParam]
public int Loops = 5;
/// Whether to test suspend/resume
[AutoParam]
public bool TestSuspend = true;
/// Whether to test constrain/unconstrain
[AutoParam]
public bool TestConstrain = true;
/// Whether to pass or fail if the target device does not support the requested action
[AutoParam]
public bool PassTestIfUnsupported = true;
///
/// overrides all the other parameters
/// S - suspend
/// R - resume
/// C - constrain
/// U - unconstrain
/// W# - wait # seconds
/// example: W10SR - wait 10 seconds, suspend the app, resume the app.
///
[AutoParam]
public string CustomSequence = "";
}
///
/// Test that stresses the game's Process Lifetime Management (PLM) functionality
///
public class PLMTest : UnrealTestNode
{
#region Actions
abstract class Action
{
abstract public bool Run(IAppInstance AppInstance, PLMTest Owner);
public override string ToString()
{
return GetType().Name.Replace("Action", "");
}
}
class ActionSuspend : Action
{
public override bool Run(IAppInstance AppInstance, PLMTest Owner)
{
return (AppInstance is IWithPLMSuspend) ? (AppInstance as IWithPLMSuspend).Suspend() : Owner.CachedConfig.PassTestIfUnsupported;
}
}
class ActionResume : Action
{
public override bool Run(IAppInstance AppInstance, PLMTest Owner)
{
return (AppInstance is IWithPLMSuspend) ? (AppInstance as IWithPLMSuspend).Resume() : Owner.CachedConfig.PassTestIfUnsupported;
}
}
class ActionConstrain : Action
{
public override bool Run(IAppInstance AppInstance, PLMTest Owner)
{
return (AppInstance is IWithPLMConstrain) ? (AppInstance as IWithPLMConstrain).Constrain() : Owner.CachedConfig.PassTestIfUnsupported;
}
}
class ActionUnconstrain : Action
{
public override bool Run(IAppInstance AppInstance, PLMTest Owner)
{
return (AppInstance is IWithPLMConstrain) ? (AppInstance as IWithPLMConstrain).Unconstrain() : Owner.CachedConfig.PassTestIfUnsupported;
}
}
class ActionWait : Action
{
readonly int WaitTimeSeconds;
public ActionWait( int InWaitTimeSeconds = 5 )
{
WaitTimeSeconds = InWaitTimeSeconds;
}
public override bool Run(IAppInstance AppInstance, PLMTest Owner)
{
System.Threading.Thread.Sleep(WaitTimeSeconds * 1000);
return true;
}
public override string ToString()
{
return $"Wait {WaitTimeSeconds}s";
}
}
/// S - suspend
/// R - resume
/// C - constrain
/// U - unconstrain
/// V - quick save
/// Q - quick resume
/// W# - wait # seconds
private Action[] ParseCustomSequence(string Sequence)
{
List Result = new List();
// only need one instance of these as they have no parameters
Action Suspend = new ActionSuspend();
Action Resume = new ActionResume();
Action Constrain = new ActionConstrain();
Action Unconstrain = new ActionUnconstrain();
// parse the string and create the sequence
int Pos = 0;
while (Pos < Sequence.Length)
{
switch (Char.ToUpper(Sequence[Pos]))
{
case 'S': Result.Add(Suspend); break;
case 'R': Result.Add(Resume); break;
case 'C': Result.Add(Constrain); break;
case 'U': Result.Add(Unconstrain); break;
case 'W':
{
Pos++;
int NumStart = Pos;
while (Pos < Sequence.Length && Char.IsNumber(Sequence[Pos]))
{
Pos++;
}
if (NumStart == Pos)
{
throw new AutomationException($"no number specified for W in custom sequence {Sequence}[{Pos}]");
}
string NumString = Sequence.Substring(NumStart, Pos - NumStart);
int Value = int.Parse(NumString);
Result.Add(new ActionWait(Value));
Pos--;
}
break;
}
Pos++;
}
return Result.ToArray();
}
Action[] GetDefaultSequence(PLMTestConfiguration Config)
{
List Result = new List();
// only need one instance of these as they have no parameters
Action Suspend = new ActionSuspend();
Action Resume = new ActionResume();
Action Constrain = new ActionConstrain();
Action Unconstrain = new ActionUnconstrain();
// build the sequence
for (int Loop = 0; Loop < Config.Loops; Loop++)
{
if (Loop == 0)
{
Result.Add(new ActionWait(Config.InitialWait));
}
else
{
Result.Add(new ActionWait(Config.LoopWait));
}
if (Config.TestSuspend)
{
Result.Add(Suspend);
Result.Add(Resume);
}
if (Config.TestConstrain)
{
Result.Add(Constrain);
Result.Add(Unconstrain);
}
}
return Result.ToArray();
}
#endregion
Action[] Actions;
int CurrentActionIndex;
bool bHasEngineInit;
///
/// Default constructor
///
///
public PLMTest(Gauntlet.UnrealTestContext InContext)
: base(InContext)
{
PLMTestConfiguration Config = GetConfiguration();
CurrentActionIndex = 0;
if (string.IsNullOrEmpty(Config.CustomSequence))
{
Actions = GetDefaultSequence(Config);
}
else
{
Actions = ParseCustomSequence(Config.CustomSequence);
}
}
///
/// Returns the configuration description for this test
///
///
public override PLMTestConfiguration GetConfiguration()
{
PLMTestConfiguration Config = base.GetConfiguration();
UnrealTestRole Client = Config.RequireRole(UnrealTargetRole.Client);
return Config;
}
///
/// Called to begin the test.
///
///
///
///
public override bool StartTest(int Pass, int InNumPasses)
{
// Call the base class to actually start the test running
if (!base.StartTest(Pass, InNumPasses))
{
return false;
}
CurrentActionIndex = 0;
bHasEngineInit = false;
return true;
}
///
/// String that we search for to be considered "Booted"
///
///
protected virtual string GetCompletionString()
{
return "Engine is initialized. Leaving FEngineLoop::Init()";
}
///
/// Called periodically while the test is running to allow code to monitor health.
///
public override void TickTest()
{
// run the base class tick;
base.TickTest();
if (!bHasEngineInit)
{
// see if the engine has been initialized yet
IAppInstance RunningInstance = this.TestInstance.RunningRoles.First().AppInstance;
UnrealLogParser LogParser = new UnrealLogParser(RunningInstance.GetLogBufferReader());
if (LogParser.GetAllContainingLines(GetCompletionString(), StringComparison.OrdinalIgnoreCase).Any())
{
Log.Info("Found '{0}'. Starting PLM tests...", GetCompletionString() );
bHasEngineInit = true;
}
}
else if (CurrentActionIndex >= Actions.Length)
{
Log.Info("All actions completed. Ending test");
MarkTestComplete();
SetUnrealTestResult(TestResult.Passed);
}
else
{
IAppInstance RunningInstance = this.TestInstance.RunningRoles.First().AppInstance;
Action CurrentAction = Actions[CurrentActionIndex];
Log.Info($"{CurrentAction}...");
// run the action and wait for it to finish
bool bSuccess = CurrentAction.Run( RunningInstance, this );
if (!bSuccess)
{
Log.Error($"{CurrentAction} failed. Ending test");
MarkTestComplete();
SetUnrealTestResult(TestResult.Failed);
}
else
{
// do the next action next tick
CurrentActionIndex++;
}
}
}
}
}