// Copyright Epic Games, Inc. All Rights Reserved.
using AutomationTool;
using System;
using System.Collections.Generic;
using System.Data;
using System.IO;
using System.Linq;
using System.Net;
using System.Threading;
using System.Drawing;
using UnrealBuildTool;
using System.Drawing.Imaging;
using ImageMagick;
namespace Gauntlet.SelfTest
{
///
/// This test validates that the UnrealSession helper class brings everything together correctly
///
[TestGroup("Unreal", 7)]
class TestUnrealSession : TestUnrealBase
{
public override void TickTest()
{
AccountPool.Initialize();
AccountPool.Instance.RegisterAccount(new EpicAccount("Foo", "Bar"));
// Add three devices to the pool
DevicePool.Instance.RegisterDevices(new ITargetDevice[] {
new TargetDeviceWindows("Local PC1", Globals.TempDir)
, new TargetDeviceWindows("Local PC2", Globals.TempDir)
});
// Create a new build (params come from our base class will be similar to "OrionGame" and "p:\builds\orion\branch-cl")
UnrealBuildSource Build = new UnrealBuildSource(this.ProjectName, this.ProjectFile, this.UnrealPath, this.UsesSharedBuildType, this.BuildPath, new string[] { "" });
// create a new options structure
UnrealOptions Options = new UnrealOptions();
// set the mapname, this will be applied automatically to the server
Options.Map = "OrionEntry";
Options.Log = true;
// add some common options.
string ServerArgs = " -nomcp -log";
// We want the client to connect to the server, so get the IP address of this PC and add it to the client args as an ExecCmd
string LocalIP = Dns.GetHostEntry(Dns.GetHostName()).AddressList.Where(o => o.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork).First().ToString();
string ClientArgs = string.Format(" -ExecCmds=\"open {0}\"", LocalIP);
// create a new session with client & server roles
string PlatformString = Gauntlet.Globals.Params.ParseValue("Platform", "Win64");
UnrealTargetPlatform Platform = UnrealTargetPlatform.Parse(PlatformString);
UnrealSession Session = new UnrealSession(Build, new[] {
new UnrealSessionRole(UnrealTargetRole.Client, Platform, Configuration, ClientArgs, Options)
, new UnrealSessionRole(UnrealTargetRole.Server, UnrealTargetPlatform.Win64, Configuration, ServerArgs, Options)
});
// launch an instance of this session
UnrealSessionInstance SessionInstance = Session.LaunchSession();
// wait for two minutes - long enough for anything to go wrong :)
DateTime StartTime = DateTime.Now;
while (SessionInstance.ClientsRunning && SessionInstance.ServerRunning)
{
if ((DateTime.Now - StartTime).TotalSeconds > 120)
{
break;
}
Thread.Sleep(1000);
}
// check these are both still running
CheckResult(SessionInstance.ClientsRunning, "Clients exited during test");
CheckResult(SessionInstance.ServerRunning, "Server exited during test");
// shutdown the session
SessionInstance.Shutdown();
// shutdown the pools
AccountPool.Shutdown();
DevicePool.Shutdown();
MarkComplete(TestResult.Passed);
}
}
class TestUnrealSessionSaveRoleArtifacts : TestUnrealBase
{
// Cached references to mocked dependencies
private UnrealSession Session;
private UnrealSessionRole Client;
private UnrealSessionRole Server;
private UnrealTestContext Context;
private UnrealSessionInstance Instance;
// Maps a role type to the SessionRole's artifact (saved) directory
private Dictionary SourceArtifactDirectories;
// Cached references to files on the
private string ClientCrashReporterFileName;
private string ClientDummyFileName;
private string ClientBuildFileName;
private DirectoryInfo ClientBuildDirectory;
private string ServerCrashReporterFileName;
private string ServerDummyFileName;
private string ServerBuildFileName;
private DirectoryInfo ServerBuildDirectory;
public TestUnrealSessionSaveRoleArtifacts()
{
if(Utils.SystemHelpers.IsNetworkPath(BuildPath))
{
throw new AutomationException("{0} expects to write temp files to the build directory. You to provide a local build path!", this);
}
SourceArtifactDirectories = new();
if (Directory.Exists(Gauntlet.Globals.LogDir))
{
Directory.Delete(Gauntlet.Globals.LogDir, true);
}
}
public override void TickTest()
{
MarkComplete();
}
public override bool StartTest(int Pass, int NumPasses)
{
MockDependencies();
// To test this, we place a few dummy files in the source artifact directory
// We then call SaveRoleArtifacts and check the destination artifact directory
// to verify files appeared as we would expect.
// We also verify the files in the source directory are no longer there
// because we want them to move intead of being copied.
try
{
// Generate some files in the source artifact directory
List SourceArtifacts = CreateSourceArtifacts();
// Call SaveRoleArtifacts
List Artifacts = Session.SaveRoleArtifacts(Context, Instance, Gauntlet.Globals.LogDir).ToList();
// Ensure the source artifacts are deleted
if (!VerifySourceFilesAreDeleted(SourceArtifacts))
{
Log.Error("Failed to verify SaveRoleArtifacts deleted source artifacts. See above for details.");
return false;
}
// Verify files exist in the destination location
if (!VerifyArtifactsAreInDestinationDirectory(Artifacts))
{
Log.Error("Failed to verify SaveRoleArtifacts moved over all expected artifact files. See above for details.");
return false;
}
}
finally
{
// Avoid dirtying the build folder
FileInfo ClientBuildFile = new(Path.Combine(ClientBuildDirectory.FullName, ClientBuildFileName));
if (ClientBuildFile.Exists)
{
ClientBuildFile.Delete();
}
FileInfo ServerBuildFile = new(Path.Combine(ServerBuildDirectory.FullName, ServerBuildFileName));
if (ServerBuildFile.Exists)
{
ServerBuildFile.Delete();
}
}
InnerStatus = TestStatus.Complete;
return true;
}
private void MockDependencies()
{
UnrealTargetPlatform Platform = UnrealTargetPlatform.Win64;
UnrealBuildSource Build = new(ProjectName, ProjectFile, UnrealPath, UsesSharedBuildType, BuildPath);
DevicePool.Instance.SetLocalOptions(Gauntlet.Globals.TempDir, false, null);
// Set up roles
UnrealDeviceTargetConstraint DefaultConstraint = new UnrealDeviceTargetConstraint(UnrealTargetPlatform.Win64);
List DefaultAdditionalDirectories = new();
Client = new UnrealSessionRole(UnrealTargetRole.Client, Platform, Configuration, null)
{
Constraint = DefaultConstraint,
AdditionalArtifactDirectories = DefaultAdditionalDirectories
};
Server = new UnrealSessionRole(UnrealTargetRole.Server, Platform, Configuration, null)
{
Constraint = DefaultConstraint,
AdditionalArtifactDirectories = DefaultAdditionalDirectories
};
List Roles = new() { Client, Server };
// TestContext
UnrealTestRoleContext ClientContext = new();
ClientContext.Type = Client.RoleType;
ClientContext.Platform = Platform;
ClientContext.Configuration = Client.Configuration;
UnrealTestRoleContext ServerContext = new();
ServerContext.Type = Server.RoleType;
ServerContext.Platform = Platform;
ServerContext.Configuration = Server.Configuration;
Dictionary RoleContexts = new()
{
{ Client.RoleType, ClientContext },
{ Server.RoleType, ServerContext }
};
Session = new UnrealSession(Build, Roles);
Session.TryReserveDevices();
TargetDeviceWindows ClientDevice = (TargetDeviceWindows)Session.UnrealDeviceReservation.ReservedDevices[0];
TargetDeviceWindows ServerDevice = (TargetDeviceWindows)Session.UnrealDeviceReservation.ReservedDevices[1];
ClientDevice.PopulateDirectoryMappings(Path.Combine(BuildPath, "WindowsClient", ProjectName));
ServerDevice.PopulateDirectoryMappings(Path.Combine(BuildPath, "WindowsServer", ProjectName));
// SessionInstance
UnrealSessionInstance.RoleInstance ClientRoleInstance = new(Client, GetAppInstanceForDevice(ClientDevice, Build.BuildName));
UnrealSessionInstance.RoleInstance ServerRoleInstance = new(Server, GetAppInstanceForDevice(ServerDevice, Build.BuildName));
UnrealSessionInstance.RoleInstance[] RoleInstances = { ClientRoleInstance, ServerRoleInstance };
ClientBuildDirectory = new (ClientRoleInstance.AppInstance.Device.GetPlatformDirectoryMappings()[EIntendedBaseCopyDirectory.Build]);
ServerBuildDirectory = new(ServerRoleInstance.AppInstance.Device.GetPlatformDirectoryMappings()[EIntendedBaseCopyDirectory.Build]);
SourceArtifactDirectories.Add(Client.RoleType, new DirectoryInfo(ClientRoleInstance.AppInstance.ArtifactPath));
SourceArtifactDirectories.Add(Server.RoleType, new DirectoryInfo(ServerRoleInstance.AppInstance.ArtifactPath));
Context = new UnrealTestContext(Build, RoleContexts, new UnrealTestOptions());
Instance = new UnrealSessionInstance(RoleInstances);
}
private List CreateSourceArtifacts()
{
DirectoryInfo ClientArtifactSource = SourceArtifactDirectories[UnrealTargetRole.Client];
DirectoryInfo ServerArtifactSource = SourceArtifactDirectories[UnrealTargetRole.Server];
string CrashReporterPath = Path.Combine("Config", "CrashReportClient", "UECC-Windows-ABCabc123");
// Client
DirectoryInfo ClientCrashReporterDirectory = new(Path.Combine(ClientArtifactSource.FullName, CrashReporterPath));
FileInfo ClientFileWithinLongCrashReporterDirectory = CreateDummyFileInDirectory(ClientCrashReporterDirectory);
FileInfo ClientBasicFile = CreateDummyFileInDirectory(ClientArtifactSource);
FileInfo ClientBuildFile = CreateDummyFileInDirectory(ClientBuildDirectory);
ClientCrashReporterFileName = ClientFileWithinLongCrashReporterDirectory.Name;
ClientDummyFileName = ClientBasicFile.Name;
ClientBuildFileName = ClientBuildFile.Name;
Client.AdditionalArtifactDirectories.Add(EIntendedBaseCopyDirectory.Build);
// Create a couple images for the client - these should get converted to a .gif
DirectoryInfo ScreenshotDirectory = new(Path.Combine(ClientArtifactSource.FullName, "Screenshots", "Windows"));
FileInfo RedImage = CreateDummyPNGInDirectory("Red", ScreenshotDirectory);
FileInfo BlueImage = CreateDummyPNGInDirectory("Blue", ScreenshotDirectory);
// Server
DirectoryInfo ServerCrashReporterDirectory = new(Path.Combine(ServerArtifactSource.FullName, CrashReporterPath));
FileInfo ServerFileWithinLongCrashReporterDirectory = CreateDummyFileInDirectory(ServerCrashReporterDirectory);
FileInfo ServerBasicFile = CreateDummyFileInDirectory(ServerArtifactSource);
FileInfo ServerBuildFile = CreateDummyFileInDirectory(ServerBuildDirectory);
ServerCrashReporterFileName = ServerFileWithinLongCrashReporterDirectory.Name;
ServerDummyFileName = ServerBasicFile.Name;
ServerBuildFileName = ServerBuildFile.Name;
Server.AdditionalArtifactDirectories.Add(EIntendedBaseCopyDirectory.Build);
return new List()
{
// Build files created to test AdditionalArtifactDirectories should be copied and not moved, so don't include them here
ClientFileWithinLongCrashReporterDirectory, ClientBasicFile,
ServerFileWithinLongCrashReporterDirectory, ServerBasicFile,
RedImage, BlueImage
};
}
private bool VerifyArtifactsAreInDestinationDirectory(List Artifacts)
{
UnrealRoleArtifacts ClientArtifacts = Artifacts.Where(Role => Role.SessionRole.RoleType == UnrealTargetRole.Client).First();
UnrealRoleArtifacts ServerArtifacts = Artifacts.Where(Role => Role.SessionRole.RoleType == UnrealTargetRole.Server).First();
DirectoryInfo ClientDirectory = new(ClientArtifacts.ArtifactPath);
DirectoryInfo ServerDirectory = new(ServerArtifacts.ArtifactPath);
string TruncatedCrashReporterPath = Path.Combine("Config", "CrashReportClient", "UECC-Windows-00");
FileInfo ClientCrashReporterFile = new(Path.Combine(ClientDirectory.FullName, TruncatedCrashReporterPath, ClientCrashReporterFileName));
FileInfo ClientBasicFile = new(Path.Combine(ClientDirectory.FullName, ClientDummyFileName));
FileInfo ClientBuildFile = new(Path.Combine(ClientDirectory.FullName, "Build", ClientBuildFileName));
// We should see the .pngs were converted to .jpgs and a .gif file in the root directory
DirectoryInfo ScreenshotDirectory = new(Path.Combine(ClientDirectory.FullName, "Screenshots", "Windows"));
FileInfo RedImage = new(Path.Combine(ScreenshotDirectory.FullName, "Red.jpg"));
FileInfo BlueImage = new(Path.Combine(ScreenshotDirectory.FullName, "Blue.jpg"));
FileInfo TestGif = new(Path.Combine(ClientDirectory.FullName, "ClientTest.gif"));
FileInfo ServerCrashReporterFile = new(Path.Combine(ServerDirectory.FullName, TruncatedCrashReporterPath, ServerCrashReporterFileName));
FileInfo ServerBasicFile = new(Path.Combine(ServerDirectory.FullName, ServerDummyFileName));
FileInfo ServerBuildFile = new(Path.Combine(ServerDirectory.FullName, "Build", ServerBuildFileName));
// A log for each process should also be generated
FileInfo ClientLog = new(Path.Combine(ClientDirectory.FullName, "ClientOutput.log"));
FileInfo ServerLog = new(Path.Combine(ServerDirectory.FullName, "ServerOutput.log"));
List FilesToVerifyExist = new()
{
ClientCrashReporterFile, ClientBasicFile, ClientBuildFile, ClientLog,
ServerCrashReporterFile, ServerBasicFile, ServerBuildFile, ServerLog,
RedImage, BlueImage, TestGif,
};
bool bSucceeded = true;
foreach (FileInfo File in FilesToVerifyExist)
{
if (!File.Exists)
{
bSucceeded = false;
Log.Error("Unable locate file {File}", File);
}
}
return bSucceeded;
}
private WindowsAppInstance GetAppInstanceForDevice(TargetDeviceWindows Device, string BuildName)
{
string ArtifactPath = Path.Combine(Device.LocalCachePath, "UserDir", "Saved");
// This lets us mock the call to StdOut
FileInfo Log = new(Path.Combine(ArtifactPath, "Logs", "Output.log"));
Log.Directory.Create();
File.WriteAllText(Log.FullName, "Foo Log");
WindowsAppInstall Install = new WindowsAppInstall(BuildName, ProjectName, Device)
{
ArtifactPath = ArtifactPath
};
LongProcessResult DummyProcess = new("cmd", "/C echo Dummy", CommandUtils.ERunOptions.NoStdOutRedirect);
WindowsAppInstance AppInstance = new(Install, DummyProcess, Log.FullName);
// This is a pretty dumb workaround, but the log file reader runs on a separate thread.
// We need to wait a few seconds to avoid accidentally moving the file in SaveRoleArtifacts
// before the thread has had time to map the contents to the stdout
Thread.Sleep(5000);
return AppInstance;
}
private FileInfo CreateDummyFileInDirectory(DirectoryInfo Directory)
{
if(!Directory.Exists)
{
Directory.Create();
}
FileInfo TempFile = new FileInfo(Path.GetTempFileName());
FileInfo DummyFile = new FileInfo(Path.Combine(Directory.FullName, TempFile.Name));
File.WriteAllText(DummyFile.FullName, "Foo");
return DummyFile;
}
private FileInfo CreateDummyPNGInDirectory(string BrushColor, DirectoryInfo Directory)
{
if (!Directory.Exists)
{
Directory.Create();
}
string ImagePath = Path.Combine(Directory.FullName, BrushColor + ".png");
Color NamedColor = Color.FromName(BrushColor);
using (MagickImage Image = new MagickImage(MagickColor.FromRgb(NamedColor.R, NamedColor.G, NamedColor.B), 16, 16))
{
Image.Format = MagickFormat.Png;
Image.Write(ImagePath);
}
return new FileInfo(ImagePath);
}
bool VerifySourceFilesAreDeleted(IEnumerable SourceFiles)
{
bool bSucceeded = true;
foreach(FileInfo File in SourceFiles)
{
File.Refresh();
if(File.Exists)
{
Log.Error("Source artifact file {File} should no longer exist, but was found!", File);
bSucceeded = false;
}
}
return bSucceeded;
}
}
}