Files
UnrealEngine/Engine/Source/Programs/AutomationTool/Gauntlet/Unreal/Automation/UE.CookOnTheFly.cs
2025-05-18 13:04:45 +08:00

289 lines
8.3 KiB
C#

// Copyright Epic Games, Inc. All Rights Reserved.
using System.Collections.Generic;
using System.Linq;
using Gauntlet;
using System.IO;
using System;
using AutomationTool;
using UnrealBuildTool;
namespace UE
{
/// <summary>
/// Runs cook on the fly 2 test
/// </summary>
public class CookOnTheFly : UnrealTestNode<UnrealTestConfiguration>
{
string CookedProjectDirectoryPath;
string CookedSettingsFilePath;
DateTime StartTime = DateTime.Now;
bool DeferredClientStarted = false;
bool ServerStarted = false;
bool ClientConnected = false;
bool CookingRequest = false;
bool CookOnTheFlyModeChange = false;
bool CookingProcessStarted = false;
IEnumerable<string> LogCategories = new string[] { "CookOnTheFly", "Cook" };
public override IEnumerable<string> GetHeartbeatLogCategories() => LogCategories;
UnrealLogStreamParser EditorLogParser = null;
UnrealLogStreamParser ClientLogParser = null;
Checker ClientCookOnTheFlyCheckers = null;
public CookOnTheFly(Gauntlet.UnrealTestContext InContext)
: base(InContext)
{
}
protected virtual string GetStartedCookServerString()
{
return "Unreal Network File Server is ready";
}
protected virtual string GetClientConnectedString()
{
return "Client connected";
}
protected virtual string GetCookingRequestString()
{
return "Received cook request";
}
protected virtual string GetCookingProcessString()
{
return "Cooked packages";
}
protected virtual string GetGameStartString()
{
return "Starting Game";
}
protected virtual string GetCookModeString()
{
return "CookMode=CookOnTheFly";
}
protected virtual string GetEngineInitializedString()
{
return "Engine is initialized";
}
protected virtual string GetReceivedPackagesCookedString()
{
return "Received 'PackagesCooked' message";
}
public override UnrealTestConfiguration GetConfiguration()
{
UnrealTestConfiguration Config = base.GetConfiguration();
UnrealTestRole EditorRole = Config.RequireRole(UnrealTargetRole.Editor);
EditorRole.CommandLine += "-run=cook -cookonthefly -zenstore -log";
UnrealTargetPlatform TargetPlatform = Context.GetRoleContext(Config.GetMainRequiredRole().Type).Platform;
CookedProjectDirectoryPath = Path.GetDirectoryName(Context.Options.ProjectPath.ToNormalizedPath());
string ProjectName = Context.Options.Project;
if (TargetPlatform == UnrealTargetPlatform.Win64)
{
CookedSettingsFilePath = CookedProjectDirectoryPath + string.Format("\\Saved\\Cooked\\Windows\\{0}\\Metadata\\CookedSettings.txt", ProjectName);
}
else
{
CookedSettingsFilePath = CookedProjectDirectoryPath + string.Format("\\Saved\\Cooked\\{0}\\{1}\\Metadata\\CookedSettings.txt", TargetPlatform.ToString(), ProjectName);
}
UnrealTestRole ClientRole = Config.RequireRole(UnrealTargetRole.Client);
string HostIP = UnrealHelpers.GetHostIpAddress();
if (HostIP == null)
{
throw new AutomationException("Could not find local IP address");
}
ClientRole.DeferredLaunch = true;
ClientRole.CommandLine += string.Format("-cookonthefly -filehostip=\"{0}\" -log -LogCmds=\"LogCookOnTheFly Verbose\"", HostIP);
return Config;
}
public override bool StartTest(int Pass, int InNumPasses)
{
if (!base.StartTest(Pass, InNumPasses))
{
return false;
}
StartTime = DateTime.Now;
return true;
}
private bool AttachToEditorLog()
{
if (TestInstance.EditorApp != null)
{
EditorLogParser = new(TestInstance.EditorApp.GetLogBufferReader());
return true;
}
return false;
}
private bool AttachToClientLog()
{
if (TestInstance.ClientApps.Any())
{
ClientLogParser = new(TestInstance.ClientApps.First().GetLogBufferReader());
ClientCookOnTheFlyCheckers = new();
ClientCookOnTheFlyCheckers.AddValidation(
"Client Engine initialization", new(() => ClientLogParser.GetLogLinesContaining(GetEngineInitializedString()).Any())
);
ClientCookOnTheFlyCheckers.AddValidation(
"Client Game started", new(() => ClientLogParser.GetLogLinesContaining(GetGameStartString()).Any())
);
ClientCookOnTheFlyCheckers.AddValidation(
"Client Received cooked packages", new(() => ClientLogParser.GetLogLinesContaining(GetReceivedPackagesCookedString()).Any())
);
return true;
}
// no client started yet.
return false;
}
public override void TickTest()
{
base.TickTest();
if (EditorLogParser == null && !AttachToEditorLog())
{
return;
}
const int TimeoutDuration = 10; // TODO Reduce the timeout to 5 minutes after finding out the reasons for the long run time.
if ((DateTime.Now - StartTime).TotalMinutes > TimeoutDuration)
{
Log.Error("No logfile activity observed in last {0} minutes. Ending test", TimeoutDuration);
MarkTestComplete();
SetUnrealTestResult(Gauntlet.TestResult.TimedOut);
}
IAppInstance EditorInstance = TestInstance.EditorApp;
if (EditorInstance == null)
{
Log.Error("Editor instance shouldn't be null");
MarkTestComplete();
SetUnrealTestResult(Gauntlet.TestResult.Failed);
}
EditorLogParser.ReadStream();
if (!ServerStarted)
{
string CompletionString = GetStartedCookServerString();
if (EditorLogParser.GetLogLinesContaining(CompletionString).Any())
{
Log.Info("Found '{0}'. Cook Server Started", CompletionString);
ServerStarted = true;
}
}
if (ServerStarted && !DeferredClientStarted)
{
if (!CookHelpers.TryLaunchDeferredRole(UnrealApp.SessionInstance, UnrealTargetRole.Client))
{
MarkTestComplete();
SetUnrealTestResult(TestResult.Failed);
return;
}
DeferredClientStarted = true;
}
if (!ClientConnected)
{
string ClientConnectedString = GetClientConnectedString();
if (EditorLogParser.GetLogLinesContaining(ClientConnectedString).Any())
{
Log.Info("Found '{0}'. Client connected", ClientConnectedString);
ClientConnected = true;
}
}
if (!CookingRequest)
{
string CookingRequestString = GetCookingRequestString();
if (EditorLogParser.GetLogLinesContaining(CookingRequestString).Any())
{
Log.Info("Found '{0}'. Cooking request exists", CookingRequestString);
CookingRequest = true;
}
}
if (!CookingProcessStarted)
{
CookingProcessStarted = EditorLogParser.GetLogLinesContaining(GetCookingProcessString()).Any();
}
if (CookingProcessStarted && !CookOnTheFlyModeChange && File.Exists(CookedSettingsFilePath))
{
string CookedSettingsFileText = File.ReadAllText(CookedSettingsFilePath);
string CookModeString = GetCookModeString();
DateTime CookedDirectoryLastModifiedTime = File.GetLastWriteTime(CookedProjectDirectoryPath + "\\Saved\\Cooked\\");
if (CookedSettingsFileText.Contains(CookModeString) && (CookedDirectoryLastModifiedTime>StartTime))
{
Log.Info("Found '{0}'. The cook mode changed for the project", CookModeString);
CookOnTheFlyModeChange = true;
}
}
if (CookOnTheFlyModeChange)
{
IAppInstance[] ClientInstances = TestInstance.ClientApps;
IAppInstance ClientInstance = null;
if (ClientInstances.Count().Equals(1))
{
ClientInstance = ClientInstances.First();
}
else
{
Log.Error("There should only be one client instance");
MarkTestComplete();
SetUnrealTestResult(Gauntlet.TestResult.Failed);
}
if (ClientInstance == null)
{
Log.Error("Client instance shouldn't be null");
MarkTestComplete();
SetUnrealTestResult(Gauntlet.TestResult.Failed);
}
if (ClientLogParser == null && !AttachToClientLog())
{
return;
}
ClientLogParser.ReadStream();
if (ClientCookOnTheFlyCheckers.PerformValidations() && CookingProcessStarted)
{
Log.Info("Found '{0}', '{1}', '{2}'. The CookOnTheFly log channel is active. The cooking process is taking place.", GetGameStartString(), GetCookingProcessString(), GetReceivedPackagesCookedString());
MarkTestComplete();
SetUnrealTestResult(Gauntlet.TestResult.Passed);
}
}
}
protected override UnrealProcessResult GetExitCodeAndReason(StopReason InReason, UnrealLog InLog, UnrealRoleArtifacts InArtifacts, out string ExitReason, out int ExitCode)
{
if (InArtifacts.SessionRole.RoleType == UnrealTargetRole.Editor)
{
InLog.EngineInitializedPattern = GetStartedCookServerString();
}
return base.GetExitCodeAndReason(InReason, InLog, InArtifacts, out ExitReason, out ExitCode);
}
}
}