Files
UnrealEngineUWP/Engine/Source/Programs/AutomationTool/LowLevelTests/RunLowLevelTests.cs
Marc Audy 0c3be2b6ad Merge Release-Engine-Staging to Test @ CL# 18240298
[CL 18241953 by Marc Audy in ue5-release-engine-test branch]
2021-11-18 14:37:34 -05:00

528 lines
16 KiB
C#

// Copyright Epic Games, Inc. All Rights Reserved.
using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using AutomationTool;
using UnrealBuildTool;
using UnrealBuildBase;
using Gauntlet;
using System.Text.RegularExpressions;
using AutomationTool.DeviceReservation;
namespace LowLevelTests
{
public class RunLowLevelTests : BuildCommand
{
public override ExitCode Execute()
{
Log.Level = LogLevel.VeryVerbose;
Globals.Params = new Params(Params);
LowLevelTestExecutorOptions ContextOptions = new LowLevelTestExecutorOptions();
AutoParam.ApplyParamsAndDefaults(ContextOptions, Globals.Params.AllArguments);
if (ContextOptions.TestApp == string.Empty)
{
Log.Error("Error: -testapp flag is missing on the command line. Expected test project that extends LowLevelTests module.");
return ExitCode.Error_Arguments;
}
if (string.IsNullOrEmpty(ContextOptions.Build))
{
Log.Error("No build path specified. Set -build= to test executable and resources directory.");
return ExitCode.Error_Arguments;
}
if (!Path.IsPathRooted(ContextOptions.Build))
{
ContextOptions.Build = Path.Combine(Globals.UnrealRootDir, ContextOptions.Build);
}
return RunTests(ContextOptions);
}
public ExitCode RunTests(LowLevelTestExecutorOptions ContextOptions)
{
UnrealTargetPlatform TestPlatform = ContextOptions.Platform;
LowLevelTestRoleContext RoleContext = new LowLevelTestRoleContext();
RoleContext.Platform = TestPlatform;
LowLevelTestsBuildSource BuildSource = new LowLevelTestsBuildSource(
ContextOptions.TestApp,
ContextOptions.Build,
ContextOptions.Platform);
SetupDevices(TestPlatform, ContextOptions);
LowLevelTestContext TestContext = new LowLevelTestContext(BuildSource, RoleContext, ContextOptions);
ITestNode NewTest = Gauntlet.Utils.TestConstructor.ConstructTest<ITestNode, LowLevelTestContext>(ContextOptions.TestApp, TestContext, new string[] { "LowLevelTests" });
if (!(NewTest is LowLevelTests))
{
throw new AutomationException("Expected ITestNode type of LowLevelTests");
}
bool TestPassed = ExecuteTest(ContextOptions, NewTest);
DevicePool.Instance.Dispose();
DoCleanup(TestPlatform);
return TestPassed ? ExitCode.Success : ExitCode.Error_TestFailure;
}
void DoCleanup(UnrealTargetPlatform Platform)
{
// TODO: Platform specific cleanup
}
private bool ExecuteTest(LowLevelTestExecutorOptions Options, ITestNode LowLevelTestNode)
{
var Executor = new TextExecutor();
try
{
bool Result = Executor.ExecuteTests(Options, new List<ITestNode>() { LowLevelTestNode });
return Result;
}
catch (Exception ex)
{
Log.Info("");
Log.Error("{0}.\r\n\r\n{1}", ex.Message, ex.StackTrace);
return false;
}
finally
{
Executor.Dispose();
if (!string.IsNullOrEmpty(Options.Device))
{
(LowLevelTestNode as LowLevelTests)
.LowLevelTestsApp
.UnrealDeviceReservation
.ReleaseDevices();
}
DevicePool.Instance.Dispose();
if (ParseParam("clean"))
{
LogInformation("Deleting temp dir {0}", Options.TempDir);
DirectoryInfo TempDirInfo = new DirectoryInfo(Options.TempDir);
if (TempDirInfo.Exists)
{
TempDirInfo.Delete(true);
}
}
GC.Collect();
}
}
protected void SetupDevices(UnrealTargetPlatform TestPlatform, LowLevelTestExecutorOptions Options)
{
Reservation.ReservationDetails = Options.JobDetails;
DevicePool.Instance.SetLocalOptions(Options.TempDir, Options.Parallel > 1, Options.DeviceURL);
DevicePool.Instance.AddLocalDevices(1);
if (!string.IsNullOrEmpty(Options.Device))
{
DevicePool.Instance.AddDevices(TestPlatform, Options.Device);
}
}
}
public class LowLevelTestExecutorOptions : TestExecutorOptions, IAutoParamNotifiable
{
public Params Params { get; protected set; }
public string TempDir;
[AutoParam("")]
public string DeviceURL;
[AutoParam("")]
public string JobDetails;
public string TestApp;
public string Build;
[AutoParam("")]
public string LogDir;
public Type BuildSourceType { get; protected set; }
[AutoParam(UnrealTargetConfiguration.Development)]
public UnrealTargetConfiguration Configuration;
public UnrealTargetPlatform Platform;
public string Device;
public LowLevelTestExecutorOptions()
{
BuildSourceType = typeof(LowLevelTestsBuildSource);
}
public virtual void ParametersWereApplied(string[] InParams)
{
Params = new Params(InParams);
if (string.IsNullOrEmpty(TempDir))
{
TempDir = Globals.TempDir;
}
else
{
Globals.TempDir = TempDir;
}
if (string.IsNullOrEmpty(LogDir))
{
LogDir = Globals.LogDir;
}
else
{
Globals.LogDir = LogDir;
}
LogDir = Path.GetFullPath(LogDir);
TempDir = Path.GetFullPath(TempDir);
Build = Params.ParseValue("build=", null);
TestApp = Globals.Params.ParseValue("testapp=", "");
string PlatformArgString = Params.ParseValue("platform=", null);
Platform = string.IsNullOrEmpty(PlatformArgString) ? BuildHostPlatform.Current.Platform : UnrealTargetPlatform.Parse(PlatformArgString);
string DeviceArgString = Params.ParseValue("device=", null);
Device = string.IsNullOrEmpty(PlatformArgString) ? "default" : DeviceArgString;
string[] CleanArgs = Params.AllArguments
.Where(Arg => !Arg.StartsWith("test=", StringComparison.OrdinalIgnoreCase)
&& !Arg.StartsWith("platform=", StringComparison.OrdinalIgnoreCase)
&& !Arg.StartsWith("device=", StringComparison.OrdinalIgnoreCase))
.ToArray();
Params = new Params(CleanArgs);
}
}
public class LowLevelTestsSession : IDisposable
{
private static int QUERY_STATE_INTERVAL = 1;
public IAppInstance Instance { get; protected set; }
private LowLevelTestsBuildSource BuildSource { get; set; }
public UnrealDeviceReservation UnrealDeviceReservation { get; private set; }
public LowLevelTestsSession(LowLevelTestsBuildSource InBuildSource)
{
BuildSource = InBuildSource;
UnrealDeviceReservation = new UnrealDeviceReservation();
}
public bool TryReserveDevices()
{
Dictionary<UnrealDeviceTargetConstraint, int> RequiredDeviceTypes = new Dictionary<UnrealDeviceTargetConstraint, int>();
// Only one device required.
RequiredDeviceTypes.Add(new UnrealDeviceTargetConstraint(BuildSource.Platform), 1);
return UnrealDeviceReservation.TryReserveDevices(RequiredDeviceTypes, 1);
}
/// <summary>
/// Copies build folder on device and launches app natively.
/// Does not retry.
/// No packaging required.
/// </summary>
public IAppInstance InstallAndRunNativeTestApp()
{
bool InstallSuccess = false;
bool RunSuccess = false;
// TargetDevice<Platform> classes have a hard dependency on UnrealAppConfig instead of IAppConfig.
// More refactoring needed to support non-packaged applications that can be run natively from a path on the device.
UnrealAppConfig AppConfig = BuildSource.GetUnrealAppConfig();
IEnumerable<ITargetDevice> DevicesToInstallOn = UnrealDeviceReservation.ReservedDevices.ToArray();
ITargetDevice Device = DevicesToInstallOn.Where(D => D.IsConnected && D.Platform == BuildSource.Platform).First();
IAppInstall Install = null;
IDeviceUsageReporter.RecordStart(Device.Name, (UnrealTargetPlatform)Device.Platform, IDeviceUsageReporter.EventType.Device, IDeviceUsageReporter.EventState.Success);
IDeviceUsageReporter.RecordStart(Device.Name, (UnrealTargetPlatform)Device.Platform, IDeviceUsageReporter.EventType.Install, IDeviceUsageReporter.EventState.Success, BuildSource.BuildName);
try
{
Install = Device.InstallApplication(AppConfig);
InstallSuccess = true;
IDeviceUsageReporter.RecordEnd(Device.Name, (UnrealTargetPlatform)Device.Platform, IDeviceUsageReporter.EventType.Install, IDeviceUsageReporter.EventState.Success);
}
catch (Exception Ex)
{
InstallSuccess = false;
Log.Info("Failed to install low level tests app onto device {0}: {1}", Device, Ex);
UnrealDeviceReservation.MarkProblemDevice(Device);
IDeviceUsageReporter.RecordEnd(Device.Name, (UnrealTargetPlatform)Device.Platform, IDeviceUsageReporter.EventType.Install, IDeviceUsageReporter.EventState.Failure);
}
if (!InstallSuccess)
{
// release all devices
UnrealDeviceReservation.ReleaseDevices();
Log.Info("\nUnable to install low level tests app.\n");
}
else
{
try
{
if (Device is IRunningStateOptions)
{
// Don't wait to detect running state and query for running state every second
IRunningStateOptions DeviceWithStateOptions = (IRunningStateOptions)Device;
DeviceWithStateOptions.WaitForRunningState = false;
DeviceWithStateOptions.CachedStateRefresh = QUERY_STATE_INTERVAL;
}
Instance = Device.Run(Install);
IDeviceUsageReporter.RecordStart(Instance.Device.Name, (UnrealTargetPlatform)Instance.Device.Platform, IDeviceUsageReporter.EventType.Test);
RunSuccess = true;
}
catch (DeviceException DeviceEx)
{
Log.Warning("Device {0} threw an exception during launch. \nException={1}", Install.Device, DeviceEx.Message);
RunSuccess = false;
}
if (RunSuccess == false)
{
Log.Warning("Failed to start low level test on {0}. Marking as problem device. Will not retry.", Device);
if (Instance != null)
{
Instance.Kill();
}
UnrealDeviceReservation.MarkProblemDevice(Device);
UnrealDeviceReservation.ReleaseDevices();
throw new AutomationException("Unable to start low level tests app, see warnings for details.");
}
}
return Instance;
}
public void Dispose()
{
if (Instance != null)
{
Instance.Kill();
IDeviceUsageReporter.RecordEnd(Instance.Device.Name, (UnrealTargetPlatform)Instance.Device.Platform, IDeviceUsageReporter.EventType.Test, IDeviceUsageReporter.EventState.Success);
if (Instance.HasExited)
{
IDeviceUsageReporter.RecordEnd(Instance.Device.Name, (UnrealTargetPlatform)Instance.Device.Platform, IDeviceUsageReporter.EventType.Test, IDeviceUsageReporter.EventState.Failure);
}
}
}
}
public class LowLevelTestRoleContext : ICloneable
{
public UnrealTargetRole Type { get { return UnrealTargetRole.Client; } }
public UnrealTargetPlatform Platform;
public UnrealTargetConfiguration Configuration { get { return UnrealTargetConfiguration.Development; } }
public object Clone()
{
return this.MemberwiseClone();
}
public override string ToString()
{
string Description = string.Format("{0} {1} {2}", Platform, Configuration, Type);
return Description;
}
};
public class LowLevelTestContext : ITestContext, ICloneable
{
public LowLevelTestsBuildSource BuildInfo { get; private set; }
public string WorkerJobID;
public LowLevelTestExecutorOptions Options { get; set; }
public Params TestParams { get; set; }
public LowLevelTestRoleContext RoleContext { get; set; }
public UnrealDeviceTargetConstraint Constraint;
public LowLevelTestContext(LowLevelTestsBuildSource InBuildInfo, LowLevelTestRoleContext InRoleContext, LowLevelTestExecutorOptions InOptions)
{
BuildInfo = InBuildInfo;
Options = InOptions;
TestParams = new Params(new string[0]);
RoleContext = InRoleContext;
}
public object Clone()
{
LowLevelTestContext Copy = (LowLevelTestContext)MemberwiseClone();
Copy.RoleContext = (LowLevelTestRoleContext)RoleContext.Clone();
return Copy;
}
public override string ToString()
{
string Description = string.Format("{0}", RoleContext);
if (WorkerJobID != null)
{
Description += " " + WorkerJobID;
}
return Description;
}
}
public interface ILowLevelTestsBuildFactory
{
bool CanSupportPlatform(UnrealTargetPlatform InPlatform);
LowLevelTestsBuild CreateBuild(UnrealTargetPlatform InPlatform, string InTestApp, string InBuildPath);
protected static string GetExecutable(UnrealTargetPlatform InPlatform, string InTestApp, string InBuildPath, string FileRegEx)
{
IEnumerable<string> Executables = DirectoryUtils.FindFiles(InBuildPath, new Regex(FileRegEx));
foreach (string Executable in Executables)
{
if (InBuildPath.ToLower().Contains(InPlatform.ToString().ToLower()))
{
if (InBuildPath.ToLower().Contains(InTestApp.ToString().ToLower()))
{
return Path.GetRelativePath(InBuildPath, Executable);
}
}
}
throw new AutomationException("Cannot find low level test executable for {0} in build path {1} for {2} using regex \"{3}\"", InPlatform, InBuildPath, InTestApp, FileRegEx);
}
protected static void CleanupUnusedFiles(UnrealTargetPlatform InPlatform, string InBuildPath)
{
try
{
string[] BuildFiles = Directory.GetFiles(InBuildPath);
foreach (string BuildFile in BuildFiles)
{
if (new FileInfo(BuildFile).Extension == ".pdb")
{
File.Delete(BuildFile);
}
}
}
catch (Exception cleanupEx)
{
Log.Error("Could not cleanup files for {0} build: {1}.", InPlatform.ToString(), cleanupEx);
}
}
}
public class DesktopLowLevelTestsBuildFactory : ILowLevelTestsBuildFactory
{
public bool CanSupportPlatform(UnrealTargetPlatform InPlatform)
{
return InPlatform.IsInGroup(UnrealPlatformGroup.Desktop);
}
public LowLevelTestsBuild CreateBuild(UnrealTargetPlatform InPlatform, string InTestApp, string InBuildPath)
{
string DesktopExecutableRegEx;
if (InPlatform == UnrealTargetPlatform.Win64)
{
DesktopExecutableRegEx = @"\w+Tests.exe$";
}
else if (InPlatform == UnrealTargetPlatform.Linux || InPlatform == UnrealTargetPlatform.Mac)
{
DesktopExecutableRegEx = @"\w+Tests$";
}
else
{
throw new AutomationException("Cannot create build for non-desktop platform " + InPlatform);
}
string ExecutablePath = ILowLevelTestsBuildFactory.GetExecutable(InPlatform, InTestApp, InBuildPath, DesktopExecutableRegEx);
return new LowLevelTestsBuild(InPlatform, InBuildPath, ExecutablePath);
}
}
public class LowLevelTestsBuildSource : IBuildSource
{
private string TestApp;
private UnrealAppConfig CachedConfig = null;
public UnrealTargetPlatform Platform { get; protected set; }
public LowLevelTestsBuild DiscoveredBuild { get; protected set; }
public LowLevelTestsBuildSource(string InTestApp, string InBuildPath, UnrealTargetPlatform InTargetPlatform)
{
TestApp = InTestApp;
Platform = InTargetPlatform;
InitBuildSource(InTestApp, InBuildPath, InTargetPlatform);
}
protected void InitBuildSource(string InTestApp, string InBuildPath, UnrealTargetPlatform InTargetPlatform)
{
ILowLevelTestsBuildFactory LowLevelTestsBuildFactory = Gauntlet.Utils.InterfaceHelpers.FindImplementations<ILowLevelTestsBuildFactory>(true)
.Where(B => B.CanSupportPlatform(InTargetPlatform))
.First();
DiscoveredBuild = LowLevelTestsBuildFactory.CreateBuild(InTargetPlatform, InTestApp, InBuildPath);
if (DiscoveredBuild == null)
{
throw new AutomationException("No builds were discovered at path {0} matching test app name {1} and target platform {2}", InBuildPath, InTestApp, InTargetPlatform);
}
}
public UnrealAppConfig GetUnrealAppConfig()
{
if (CachedConfig == null)
{
CachedConfig = new UnrealAppConfig();
CachedConfig.Name = BuildName;
CachedConfig.ProjectName = TestApp;
CachedConfig.ProcessType = UnrealTargetRole.Client;
CachedConfig.Platform = Platform;
CachedConfig.Configuration = UnrealTargetConfiguration.Development;
CachedConfig.Build = DiscoveredBuild;
CachedConfig.Sandbox = "LowLevelTests";
CachedConfig.FilesToCopy = new List<UnrealFileToCopy>();
}
return CachedConfig;
}
public bool CanSupportPlatform(UnrealTargetPlatform Platform)
{
return true;
}
public string BuildName { get { return TestApp; } }
}
public class LowLevelTestsBuild : StagedBuild
{
public LowLevelTestsBuild(UnrealTargetPlatform InPlatform, string InBuildPath, string InExecutablePath)
: base(InPlatform, UnrealTargetConfiguration.Development, UnrealTargetRole.Client, InBuildPath, InExecutablePath)
{
Platform = InPlatform;
BuildPath = InBuildPath;
ExecutablePath = InExecutablePath;
Flags = BuildFlags.CanReplaceCommandLine | BuildFlags.CanReplaceExecutable | BuildFlags.Loose;
}
}
}