// 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++; } } } } }