// Copyright Epic Games, Inc. All Rights Reserved. using AutomationTool; using EpicGame; using Gauntlet; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text.RegularExpressions; namespace UE { /// /// Test that waits for the client and server to get to the front-end then quits /// public class BootTest : UnrealTestNode { /// /// Used to track progress via logging /// UnrealLogStreamParser LogReader = null; /// /// Time we last saw a change in logging /// DateTime LastLogTime = DateTime.Now; /// /// Set to true once we detect the game has launched correctly /// bool DidDetectLaunch = false; /// /// Log idle timeout in seconds /// private float kTimeOutDuration = 10 * 60; /// /// Default constructor /// /// public BootTest(Gauntlet.UnrealTestContext InContext) : base(InContext) { } /// /// Returns the configuration description for this test /// /// public override UnrealTestConfiguration GetConfiguration() { UnrealTestConfiguration Config = base.GetConfiguration(); UnrealTestRole Client = Config.RequireRole(UnrealTargetRole.Client); if (string.IsNullOrEmpty(GetCompletionString())) { Client.CommandLineParams.Add("ExecCmds", "Automation SoftQuit"); } float logIdleTimeout = Globals.Params.ParseValue("LogIdleTimeout", kTimeOutDuration); if (logIdleTimeout > 0) { kTimeOutDuration = logIdleTimeout; } 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; } // track our starting condition LastLogTime = DateTime.Now; LogReader = null; DidDetectLaunch = false; return true; } /// /// String that we search for to be considered "Booted" /// /// protected virtual string GetCompletionString() { // Intentionally setup with no completion string as UnrealTestNode verified initialization string already return null; } /// /// Called periodically while the test is running to allow code to monitor health. /// public override void TickTest() { // run the base class tick; base.TickTest(); // Get the log of the first client app IAppInstance RunningInstance = this.TestInstance.RunningRoles.First().AppInstance; if (LogReader == null) { LogReader = new UnrealLogStreamParser(RunningInstance.GetLogBufferReader()); } LogReader.ReadStream(); IEnumerable BusyLogLines = LogReader.GetLogFromEditorBusyChannels(); if (BusyLogLines.Any()) { LastLogTime = DateTime.Now; // log new entries so people have something to look at BusyLogLines.ToList().ForEach(S => Log.Info("{0}", S)); } // Gauntlet will timeout tests based on the -timeout argument, but we have greater insight here so can bail earlier to save // tests from idling on the farm needlessly. if ((DateTime.Now - LastLogTime).TotalSeconds > kTimeOutDuration) { Log.Error("No logfile activity observed in last {Time:0.00} minutes. Ending test", kTimeOutDuration / 60); MarkTestComplete(); SetUnrealTestResult(TestResult.TimedOut); } string CompletionString = GetCompletionString(); if (!string.IsNullOrEmpty(CompletionString)) { if (LogReader.GetLogLinesContaining(CompletionString).Any()) { Log.Info("Found '{0}'. Ending Test", GetCompletionString()); MarkTestComplete(); DidDetectLaunch = true; SetUnrealTestResult(TestResult.Passed); } } } /// /// Called after a test finishes to create an overall summary based on looking at the artifacts /// /// /// /// ITestReport /// /// /// public override ITestReport CreateReport(TestResult Result, UnrealTestContext Context, UnrealBuildSource Build, IEnumerable InResults, string InArtifactPath) { if (Result == TestResult.Passed) { if (!string.IsNullOrEmpty(GetCompletionString()) && !DidDetectLaunch) { ReportError("Failed to detect completion of launch"); } else { // find a logfile or something that indicates the process ran successsfully bool MissingLogs = false; foreach (var RoleResult in InResults) { if (!File.Exists(RoleResult.Artifacts.LogPath)) { MissingLogs = true; ReportError("No log files found for {0}. Were they not retrieved from the device?", RoleResult.Artifacts.SessionRole); } } if (!MissingLogs) { Log.Info("Found valid log artifacts for test"); } } } return base.CreateReport(GetTestResult()); } } /// /// Test that verifies the editor boots /// public class EditorBootTest : BootTest { public EditorBootTest(Gauntlet.UnrealTestContext InContext) : base(InContext) { } /// /// Returns the configuration description for this test /// /// public override UnrealTestConfiguration GetConfiguration() { UnrealTestConfiguration Config = base.GetConfiguration(); // currently needed as BootTest isn't an abstract class. Can be changed for 4.27 Config.ClearRoles(); UnrealTestRole EditorRole = Config.RequireRole(Config.CookedEditor ? UnrealTargetRole.CookedEditor : UnrealTargetRole.Editor); EditorRole.CommandLineParams.Add("execcmds", "QUIT_EDITOR"); return Config; } protected override string GetCompletionString() { return null; } } /// /// Test that verifies a target boots /// public class TargetBootTest : BootTest { public TargetBootTest(Gauntlet.UnrealTestContext InContext) : base(InContext) { } /// /// Returns the configuration description for this test /// /// public override UnrealTestConfiguration GetConfiguration() { UnrealTestConfiguration Config = base.GetConfiguration(); Config.RequireRole(UnrealTargetRole.Client); return Config; } } } // Provided for backwards compatibility with scripts namespace Gauntlet.UnrealTest { class BootTest : UE.BootTest { /// /// Default constructor /// /// public BootTest(Gauntlet.UnrealTestContext InContext) : base(InContext) { Log.Warning("Gauntlet.UnrealTest.BootTest is deprecated and will be removed in 4.27. Use UE.BootTest. All arguments are the same"); } } }