Files
UnrealEngineUWP/Engine/Source/Programs/AutomationTool/Scripts/Tests.Automation.cs
Ben Marsh 28be3c41b4 Copying //UE4/Dev-Build to //UE4/Dev-Main (Source: //UE4/Dev-Build @ 3075869)
#lockdown Nick.Penwarden
#rb none

==========================
MAJOR FEATURES + CHANGES
==========================

Change 3059740 on 2016/07/21 by Ben.Marsh

	Format license violations on separate lines rather than as a single multi-line string, so they can be parsed out correctly by the EC log parser.

Change 3063115 on 2016/07/25 by Ben.Marsh

	Add missing projects to Mono AutomationTool solution.

Change 3067125 on 2016/07/27 by Ben.Marsh

	Clean up and document the engine versioning system.

	* Version.h has a summary of all the different versioning concepts in UE4, as well as an explanation of what all the macros do.
	* VersionLocked.h has been removed. ENGINE_NET_VERSION and ENGINE_REPLAY_VERSION can be overriden directly in Version.h if necessary.
	* Added explicit macros for ENGINE_CURRENT_CL_VERSION And ENGINE_COMPATIBLE_CL_VERSION. The MODULE_API_VERSION macro is now defined to be the same as ENGINE_COMPATIBLE_CL_VERSION.
	* Build/Build.version now includes the compatible CL version, so it can be determined without having to parse header files inside MacToolChain.cs.
	* UpdateLocalVersion UAT command now exposes a -CompatibleCL=... argument to update ENGINE_COMPATIBLE_CL_VERSION and the CompatibleChange property in Build.version.
	* SetVersion BuildGraph task now has a CompatibleChange="" attribute to update ENGINE_COMPATIBLE_CL_VERSION and the CompatibleChange property in Build.version.
	* VersionFileUpdater now makes an effort to parse tokens matching the macros it's going to overwrite, rather than requiring exact matches in whitespace in Version.h.
	* Running UpdateLocalVersion now syncs files to revision 0 before writing if P4 is enabled, similarly to UGS, or fails if they are not writable.

Change 3067242 on 2016/07/27 by Ben.Marsh

	Fix UGS showing a build failure/warning dialog immediately if you submit while the build is yellow/red. Now ignores builds before your last submitted CL.

Change 3068116 on 2016/07/28 by Ben.Marsh

	Add a small Windows utility to terminate all running processes under a given root directory, and run it before and after every sync. Mean to fix instances where zombie editor processes do not terminate correctly and cause subsequent syncs to fail.

Change 3068246 on 2016/07/28 by Ben.Marsh

	UGS: Improvements to 'Clean Workspace' dialog - empty folders are now set to be deleted by default, and added a context menu to select all/none/empty/default for a subtree, as well as to open Windows explorer at that location.

Change 3068573 on 2016/07/28 by Ben.Marsh

	Attempt to fix AppDomain unload errors; suspect caused by delay to terminate child processes when managed exception is thrown on child threads. Now waits for the currently running threads to finish executing before quitting.

Change 3068575 on 2016/07/28 by Ben.Marsh

	Respect the -noxge command-line argument when compiling UHT.

Change 3068665 on 2016/07/28 by Ben.Marsh

	Delete any DLLs in output folders which share names with build products. The Windows loader reads DLLs from the first location it finds a file with a matching name, so we need to ensure that it doesn't load stale DLLs when output directories are changed (moving a module into a plugin, for example).

Change 3073316 on 2016/08/02 by Ben.Marsh

	CoreUObject: Replace header guards with #pragma once directives.

Change 3073325 on 2016/08/02 by Ben.Marsh

	CoreUObject: Manually untangle a few circular includes around ObjectBase.h and Class.h to allow making them into IWYU-style headers.

Change 3074391 on 2016/08/02 by Ben.Marsh

	GitHub #2646: Always allow programs to build as part of non-game solution configurations.

Change 3075123 on 2016/08/03 by Richard.Fawcett

	Ensure that zip files created outside of Ionic Zip can be unzipped with CommandUtils.UnzipFiles

	Zip files created with 7-zip, WinRAR, and Windows Explorer all contain entities for directories, not just the files in them.  Code in CommandUtils.UnzipFiles made the assumption that all entities in the zip file were files, and an exception was being thrown on encountering a directory.

Change 3075848 on 2016/08/03 by Ben.Marsh

	UBT: Fix a few cases where it's possible to construct a non-canonical DirectoryReference - root directories on Windows must always have a terminating slash, but any other directories must note. DirectoryInfo and Path.GetFullPath() do not enforce this requirement.

Change 3075850 on 2016/08/03 by Ben.Marsh

	UBT: Fix UHT failures when running with -nopch.

Change 3077058 by Ben.Marsh

	UHT: Remove the auto-generated ObjectBase.h include from generated header files. Was originally added to allow doxygen to parse headers more easily, but was switched to parsing the file only as part of parent files instead.

[CL 3077308 by Ben Marsh in Main branch]
2016-08-04 10:41:07 -04:00

1714 lines
55 KiB
C#

// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved.
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Security;
using System.Security.Principal;
using System.Security.Permissions;
using System.Runtime.InteropServices;
using System.Runtime.ConstrainedExecution;
using Microsoft.Win32.SafeHandles;
using AutomationTool;
using System.Threading;
using UnrealBuildTool;
[Help("Tests P4 functionality. Runs 'p4 info'.")]
[RequireP4]
[DoesNotNeedP4CL]
class TestP4_Info : BuildCommand
{
public override void ExecuteBuild()
{
Log("P4CLIENT={0}", GetEnvVar("P4CLIENT"));
Log("P4PORT={0}", GetEnvVar("P4PORT"));
var Result = P4.P4("info");
if (Result != 0)
{
throw new AutomationException("p4 info failed: {0}", Result.Output);
}
}
}
[Help("GitPullRequest")]
[Help("Dir=", "Directory of the Git repo.")]
[Help("PR=", "PR # to shelve, can use a range PR=5-25")]
[RequireP4]
[DoesNotNeedP4CL]
class GitPullRequest : BuildCommand
{
/// URL to our UnrealEngine repository on GitHub
static readonly string GitRepositoryURL_Engine = "https://github.com/EpicGames/UnrealEngine.git";
static readonly string GitRepositoryURL_UT = "https://github.com/EpicGames/UnrealTournament.git";
string GitRepositoryURL = null;
bool bDoingUT = false;
string FindExeFromPath(string ExeName, string ExpectedPathSubstring = null)
{
if (File.Exists(ExeName))
{
return Path.GetFullPath(ExeName);
}
foreach (string BasePath in Environment.GetEnvironmentVariable("PATH").Split(Path.PathSeparator))
{
var FullPath = Path.Combine(BasePath, ExeName);
if (ExpectedPathSubstring == null || FullPath.IndexOf(ExpectedPathSubstring, StringComparison.InvariantCultureIgnoreCase) != -1)
{
if (File.Exists(FullPath))
{
return FullPath;
}
}
}
return null;
}
string RunGit(string GitCommandLine)
{
string GitExePath = FindExeFromPath("Git.exe", "PortableGit");
if (GitExePath == null)
{
throw new AutomationException("Unable to find Git.exe in the system path under a 'PortableGit' subdirectory. Make sure the GitHub client is installed, and you are running this script from within a GitHub Command Shell. We want to make sure we're using the correct version of Git, in case multiple versions are on the computer, which is why we check for a PortableGit folder that the GitHub Shell uses.");
}
Log("Running {0} {1}", GitExePath, GitCommandLine);
ProcessResult Result = Run(App: GitExePath, CommandLine: GitCommandLine, Options: (ERunOptions.NoLoggingOfRunCommand | ERunOptions.AllowSpew | ERunOptions.AppMustExist));
if (Result > 0 || Result < 0)
{
throw new AutomationException(String.Format("Command failed (Result:{2}): {0} {1}", GitExePath, GitCommandLine, Result.ExitCode));
}
// Return the results (sans leading and trailing whitespace)
return Result.Output.Trim();
}
bool ScanForBranchAndCL_BaseVersion(string GitCommand, out string Depot, out int CL)
{
Depot = null;
CL = 0;
try
{
var Base = RunGit(GitCommand);
string BaseStart = "Engine source (";
string BaseEnd = ")";
if (Base.Contains(BaseStart) && Base.Contains(BaseEnd))
{
Base = Base.Substring(Base.IndexOf(BaseStart) + BaseStart.Length);
if (Base.StartsWith("4."))
{
Depot = "//depot/UE4-Releases/4." + Base.Substring(2, 1);
}
else if (Base.StartsWith("Main"))
{
Depot = "//depot/UE4";
}
else if (Base.StartsWith("UT"))
{
Depot = "//depot/UE4-UT";
}
else
{
throw new AutomationException("Unrecognized branch.");
}
Log("Depot {0}", Depot);
Base = Base.Substring(0, Base.IndexOf(BaseEnd));
if (!Base.Contains(" "))
{
throw new AutomationException("Unrecognized commit3.");
}
Base = Base.Substring(Base.LastIndexOf(" "));
Log("CL String {0}", Base);
CL = int.Parse(Base);
}
Log("CL int {0}", CL);
if (CL < 2000000 || String.IsNullOrWhiteSpace(Depot))
{
throw new AutomationException("Unrecognized commit3.");
}
return true;
}
catch (Exception)
{
CL = 0;
return false;
}
}
bool ScanForBranchAndCL_LiveVersion(string GitCommand, out string Depot, out int CL)
{
Depot = null;
CL = 0;
try
{
var Base = RunGit(GitCommand);
string LiveStart = "[CL ";
string LiveEnd = " branch]";
if (Base.Contains(LiveStart) && Base.Contains(LiveEnd))
{
var CLStuff = Base.Substring(Base.IndexOf(LiveStart) + LiveStart.Length);
if (CLStuff.IndexOf(" ") <= 0)
{
throw new AutomationException("Unrecognized commit5.");
}
CLStuff = CLStuff.Substring(0, CLStuff.IndexOf(" "));
Log("CL String {0}", CLStuff);
CL = int.Parse(CLStuff);
var BranchStuff = Base.Substring(Base.IndexOf(LiveStart) + LiveStart.Length, Base.IndexOf(LiveEnd) - Base.IndexOf(LiveStart) - LiveStart.Length);
if (BranchStuff.IndexOf(" ") <= 0)
{
throw new AutomationException("Unrecognized commit6.");
}
BranchStuff = BranchStuff.Substring(BranchStuff.LastIndexOf(" ") + 1);
Log("Branch String {0}", BranchStuff);
if (BranchStuff.StartsWith("4."))
{
Depot = "//depot/UE4-Releases/4." + BranchStuff.Substring(2, 1);
}
else if (BranchStuff.StartsWith("Main"))
{
Depot = "//depot/UE4";
}
else if (BranchStuff.StartsWith("UT"))
{
Depot = "//depot/UE4-UT";
}
else
{
throw new AutomationException("Unrecognized branch2.");
}
}
Log("CL int {0}", CL);
if (CL < 2000000 || String.IsNullOrWhiteSpace(Depot))
{
throw new AutomationException("Unrecognized commit3.");
}
return true;
}
catch (Exception)
{
CL = 0;
return false;
}
}
void ExecuteInner(string Dir, int PR)
{
string PRNum = PR.ToString();
// Discard any old changes we may have committed
RunGit("reset --hard");
// show-ref is just a double check that the PR exists
//var Refs = RunGit("show-ref");
/*if (!Refs.Contains("refs/remotes/origin/pr/" + PRNum))
{
throw new AutomationException("This is not among the refs: refs/remotes/origin/pr/{0}", PRNum);
}*/
RunGit(String.Format("fetch origin refs/pull/{0}/head:pr/{1}", PRNum, PRNum));
RunGit(String.Format("checkout pr/{0} --", PRNum));
int CLBase;
string DepotBase;
ScanForBranchAndCL_BaseVersion(String.Format("log --author=TimSweeney --author=UnrealBot -100 pr/{0} --", PRNum), out DepotBase, out CLBase);
int CLLive;
string DepotLive;
ScanForBranchAndCL_LiveVersion(String.Format("log -100 pr/{0} --", PRNum), out DepotLive, out CLLive);
if (CLLive == 0 && CLBase == 0)
{
throw new AutomationException("Could not find a base change and branch using either method.");
}
int CL = 0;
string Depot = "";
if (CLBase > CLLive)
{
CL = CLBase;
Depot = DepotBase;
}
else
{
CL = CLLive;
Depot = DepotLive;
}
if (CL < 2000000 || String.IsNullOrWhiteSpace(Depot))
{
throw new AutomationException("Could not find a base change and branch using either method.");
}
P4ClientInfo NewClient = P4.GetClientInfo(P4Env.Client);
foreach (var p in NewClient.View)
{
Log("{0} = {1}", p.Key, p.Value);
}
string TestClient = P4Env.User + "_" + Environment.MachineName + "_PullRequests_" + Depot.Replace("/", "_");
NewClient.Owner = P4Env.User;
NewClient.Host = Environment.MachineName;
NewClient.RootPath = Dir;
NewClient.Name = TestClient;
NewClient.View = new List<KeyValuePair<string, string>>();
NewClient.View.Add(new KeyValuePair<string, string>(Depot + "/...", "/..."));
NewClient.Stream = null;
if (!P4.DoesClientExist(TestClient))
{
P4.CreateClient(NewClient);
}
var P4Sub = new P4Connection(P4Env.User, TestClient, P4Env.P4Port);
P4Sub.Sync(String.Format("-f -k -q {0}/...@{1}", Depot, CL));
var Change = P4Sub.CreateChange(null, String.Format("GitHub pull request #{0}", PRNum));
P4Sub.ReconcileNoDeletes(Change, CommandUtils.MakePathSafeToUseWithCommandLine(CombinePaths(Dir, bDoingUT ? "UnrealTournament" : "Engine", "...")));
P4Sub.Shelve(Change);
P4Sub.Revert(Change, "-k //...");
}
public override void ExecuteBuild()
{
if (ParseParam("UT"))
{
bDoingUT = true;
GitRepositoryURL = GitRepositoryURL_UT;
}
else
{
bDoingUT = false;
GitRepositoryURL = GitRepositoryURL_Engine;
}
var Dir = ParseParamValue("Dir");
if (String.IsNullOrEmpty(Dir))
{
// No Git repo directory was specified, so we'll choose a directory automatically
Dir = Path.GetFullPath(Path.Combine(CmdEnv.LocalRoot, "Engine", "Intermediate", bDoingUT ? "PullRequestGitRepo_UT" : "PullRequestGitRepo"));
}
var PRNum = ParseParamValue("PR");
if (String.IsNullOrEmpty(PRNum))
{
throw new AutomationException("Must Provide PR arg, the number of the PR, or a range.");
}
int PRMin;
int PRMax;
if (PRNum.Contains("-"))
{
var Nums = PRNum.Split("-".ToCharArray());
PRMin = int.Parse(Nums[0]);
PRMax = int.Parse(Nums[1]);
}
else
{
PRMin = int.Parse(PRNum);
PRMax = PRMin;
}
var Failures = new List<string>();
// Setup Git repo
{
if (ParseParam("Clean"))
{
Console.WriteLine("Cleaning temporary Git repository folder... ");
// Wipe the Git repo directory
if (!InternalUtils.SafeDeleteDirectory(Dir))
{
throw new AutomationException("Unable to clean out temporary Git repo directory: " + Dir);
}
}
// Change directory to the Git repository
bool bRepoAlreadyExists = InternalUtils.SafeDirectoryExists(Dir);
if (!bRepoAlreadyExists)
{
InternalUtils.SafeCreateDirectory(Dir);
}
PushDir(Dir);
if (!bRepoAlreadyExists)
{
// Don't init Git if we didn't clean, because the old repo is probably still there.
// Create the Git repository
RunGit("init");
}
// Make sure that creating the repository worked OK
RunGit("status");
// Check to see if we already have a remote origin setup
{
string Result = RunGit("remote -v");
if (Result == "origin")
{
// OK, we already have an origin but no remote is associated with it. We'll do that now.
RunGit("remote set-url origin " + GitRepositoryURL);
}
else if (Result.IndexOf(GitRepositoryURL, StringComparison.InvariantCultureIgnoreCase) != -1)
{
// Origin is already setup! Nothing to do.
}
else
{
// No origin is set, so let's add it!
RunGit("remote add origin " + GitRepositoryURL);
}
}
// Fetch all of the remote branches/tags into our local index. This is needed so that we can figure out
// which branches exist already.
RunGit("fetch");
}
for (int PR = PRMin; PR <= PRMax; PR++)
{
try
{
ExecuteInner(Dir, PR);
}
catch (Exception Ex)
{
Log(" Exception was {0}", LogUtils.FormatException(Ex));
Failures.Add(String.Format("PR {0} Failed with {1}", PR, LogUtils.FormatException(Ex)));
}
}
PopDir();
foreach (var Failed in Failures)
{
Log("{0}", Failed);
}
}
}
[Help("Throws an automation exception.")]
class TestFail : BuildCommand
{
public override void ExecuteBuild()
{
throw new AutomationException("TestFail throwing an exception, cause that is what I do.");
}
}
[Help("Prints a message and returns success.")]
class TestSuccess : BuildCommand
{
public override void ExecuteBuild()
{
Log("TestSuccess message.");
}
}
[Help("Prints a message and returns success.")]
class TestMessage : BuildCommand
{
public override void ExecuteBuild()
{
Log("TestMessage message.");
Log("you must be in error");
Log("Behold the Error of you ways");
Log("ERROR, you are silly");
Log("ERROR: Something must be broken");
}
}
[Help("Calls UAT recursively with a given command line.")]
class TestRecursion : BuildCommand
{
public override void ExecuteBuild()
{
Log("TestRecursion *********************");
string Params = ParseParamValue("Cmd");
Log("Recursive Command: {0}", Params);
RunUAT(CmdEnv, Params);
}
}
[Help("Calls UAT recursively with a given command line.")]
class TestRecursionAuto : BuildCommand
{
public override void ExecuteBuild()
{
Log("TestRecursionAuto *********************");
RunUAT(CmdEnv, "TestMessage");
RunUAT(CmdEnv, "TestMessage");
RunUAT(CmdEnv, "TestRecursion -Cmd=TestMessage");
RunUAT(CmdEnv, "TestRecursion -Cmd=TestFail");
}
}
[Help("Makes a zip file in Rocket/QFE")]
class TestMacZip : BuildCommand
{
public override void ExecuteBuild()
{
Log("TestMacZip *********************");
if (UnrealBuildTool.Utils.IsRunningOnMono)
{
PushDir(CombinePaths(CmdEnv.LocalRoot, "Rocket/QFE"));
RunAndLog(CommandUtils.CmdEnv, "zip", "-r TestZip .");
PopDir();
}
else
{
throw new AutomationException("This probably only works on the mac.");
}
}
}
[Help("Tests P4 functionality. Creates a new changelist under the workspace %P4CLIENT%")]
[RequireP4]
class TestP4_CreateChangelist : BuildCommand
{
public override void ExecuteBuild()
{
Log("P4CLIENT={0}", GetEnvVar("P4CLIENT"));
Log("P4PORT={0}", GetEnvVar("P4PORT"));
var CLDescription = "AutomationTool TestP4";
Log("Creating new changelist \"{0}\" using client \"{1}\"", CLDescription, GetEnvVar("P4CLIENT"));
var ChangelistNumber = P4.CreateChange(Description: CLDescription);
Log("Created changelist {0}", ChangelistNumber);
}
}
[RequireP4]
class TestP4_StrandCheckout : BuildCommand
{
public override void ExecuteBuild()
{
int WorkingCL = P4.CreateChange(P4Env.Client, String.Format("TestP4_StrandCheckout, head={0}", P4Env.Changelist));
Log("Build from {0} Working in {1}", P4Env.Changelist, WorkingCL);
List<string> Sign = new List<string>();
Sign.Add(CombinePaths(CmdEnv.LocalRoot, @"\Engine\Binaries\DotNET\AgentInterface.dll"));
Log("Signing and adding {0} build products to changelist {1}...", Sign.Count, WorkingCL);
CodeSign.SignMultipleIfEXEOrDLL(this, Sign);
foreach (var File in Sign)
{
P4.Sync("-f -k " + File + "#head"); // sync the file without overwriting local one
if (!FileExists(File))
{
throw new AutomationException("BUILD FAILED {0} was a build product but no longer exists", File);
}
P4.ReconcileNoDeletes(WorkingCL, File);
}
}
}
[RequireP4]
class TestP4_LabelDescription : BuildCommand
{
public override void ExecuteBuild()
{
string Label = GetEnvVar("SBF_LabelFromUser");
string Desc;
Log("LabelDescription {0}", Label);
var Result = P4.LabelDescription(Label, out Desc);
if (!Result)
{
throw new AutomationException("Could not get label description");
}
Log("Result***\n{0}\n***\n", Desc);
}
}
[RequireP4]
class TestP4_ClientOps : BuildCommand
{
public override void ExecuteBuild()
{
string TemplateClient = "ue4_licensee_workspace";
var Clients = P4.GetClientsForUser("UE4_Licensee");
string TestClient = "UAT_Test_Client";
P4ClientInfo NewClient = null;
foreach (var Client in Clients)
{
if (Client.Name == TemplateClient)
{
NewClient = Client;
break;
}
}
if (NewClient == null)
{
throw new AutomationException("Could not find template");
}
NewClient.Owner = P4Env.User; // this is not right, we need the actual licensee user!
NewClient.Host = Environment.MachineName.ToLower();
NewClient.RootPath = @"C:\TestClient";
NewClient.Name = TestClient;
if (P4.DoesClientExist(TestClient))
{
P4.DeleteClient(TestClient);
}
P4.CreateClient(NewClient);
//P4CLIENT Name of client workspace p4 help client
//P4PASSWD User password passed to server p4 help passwd
string[] VarsToSave =
{
"P4CLIENT",
//"P4PASSWD",
};
var KeyValues = new Dictionary<string, string>();
foreach (var ToSave in VarsToSave)
{
KeyValues.Add(ToSave, GetEnvVar(ToSave));
}
SetEnvVar("P4CLIENT", NewClient.Name);
//SetEnv("P4PASSWD", ParseParamValue("LicenseePassword");
//Sync(CombinePaths(PathSeparator.Slash, P4Env.BuildRootP4, "UE4Games.uprojectdirs"));
string Output;
P4.P4Output(out Output, "files -m 10 " + CombinePaths(PathSeparator.Slash, P4Env.BuildRootP4, "..."));
var Lines = Output.Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries);
string SlashRoot = CombinePaths(PathSeparator.Slash, P4Env.BuildRootP4, "/");
string LocalRoot = CombinePaths(CmdEnv.LocalRoot, @"\");
foreach (string Line in Lines)
{
if (Line.Contains(" - ") && Line.Contains("#"))
{
string depot = Line.Substring(0, Line.IndexOf("#"));
if (!depot.Contains(SlashRoot))
{
throw new AutomationException("{0} does not contain {1} and it is supposed to.", depot, P4Env.BuildRootP4);
}
string local = CombinePaths(depot.Replace(SlashRoot, LocalRoot));
Log("{0}", depot);
Log(" {0}", local);
}
}
// should be used as a sanity check! make sure there are no files!
P4.LogP4Output(out Output, "files -m 10 " + CombinePaths(PathSeparator.Slash, P4Env.BuildRootP4, "...", "NoRedist", "..."));
// caution this doesn't actually use the client _view_ from the template at all!
// if you want that sync -f -k /...
// then use client syntax in the files command instead
// don't think we care, we don't rely on licensee _views_
foreach (var ToLoad in VarsToSave)
{
SetEnvVar(ToLoad, KeyValues[ToLoad]);
}
P4.DeleteClient(TestClient);
}
}
class CleanDDC : BuildCommand
{
public override void ExecuteBuild()
{
Log("*********************** Clean DDC");
bool DoIt = ParseParam("DoIt");
for (int i = 0; i < 10; i++)
{
for (int j = 0; j < 10; j++)
{
for (int k = 0; k < 10; k++)
{
string Dir = CombinePaths(String.Format(@"P:\UE4DDC\{0}\{1}\{2}", i, j, k));
if (!DirectoryExists_NoExceptions(Dir))
{
throw new AutomationException("Missing DDC dir {0}", Dir);
}
var files = FindFiles_NoExceptions("*.*", false, Dir);
foreach (var file in files)
{
if (FileExists_NoExceptions(file))
{
FileInfo Info = new FileInfo(file);
Log("{0}", file);
if ((DateTime.UtcNow - Info.LastWriteTimeUtc).TotalDays > 20)
{
Log(" is old.");
if (DoIt)
{
DeleteFile_NoExceptions(file);
}
}
else
{
Log(" is NOT old.");
}
}
}
}
}
}
}
}
class TestTestFarm : BuildCommand
{
public override void ExecuteBuild()
{
Log("*********************** TestTestFarm");
string Exe = CombinePaths(CmdEnv.LocalRoot, "Engine", "Binaries", "Win64", "UE4Editor.exe");
string ClientLogFile = CombinePaths(CmdEnv.LogFolder, "HoverGameRun");
string CmdLine = " ../../../Samples/HoverShip/HoverShip.uproject -game -forcelogflush -log -abslog=" + ClientLogFile;
ProcessResult App = Run(Exe, CmdLine, null, ERunOptions.AllowSpew | ERunOptions.NoWaitForExit | ERunOptions.AppMustExist | ERunOptions.NoStdOutRedirect);
LogFileReaderProcess(ClientLogFile, App, (string Output) =>
{
if (!String.IsNullOrEmpty(Output) &&
Output.Contains("Game Engine Initialized"))
{
Log("It looks started, let me kill it....");
App.StopProcess();
}
return true; //keep reading
});
}
}
[Help("Test command line argument parsing functions.")]
class TestArguments : BuildCommand
{
public override void ExecuteBuild()
{
Log("List of provided arguments: ");
for (int Index = 0; Index < Params.Length; ++Index)
{
Log("{0}={1}", Index, Params[Index].ToString());
}
object[] TestArgs = new object[] { "NoXGE", "SomeArg", "Map=AwesomeMap", "run=whatever", "Stuff" };
if (!ParseParam(TestArgs, "noxge"))
{
throw new AutomationException("ParseParam test failed.");
}
if (ParseParam(TestArgs, "Dude"))
{
throw new AutomationException("ParseParam test failed.");
}
if (!ParseParam(TestArgs, "Stuff"))
{
throw new AutomationException("ParseParam test failed.");
}
string Val = ParseParamValue(TestArgs, "map");
if (Val != "AwesomeMap")
{
throw new AutomationException("ParseParamValue test failed.");
}
Val = ParseParamValue(TestArgs, "run");
if (Val != "whatever")
{
throw new AutomationException("ParseParamValue test failed.");
}
}
}
[Help("Checks if combine paths works as excpected")]
public class TestCombinePaths : BuildCommand
{
public override void ExecuteBuild()
{
var Results = new string[]
{
CombinePaths("This/", "Is", "A", "Test"),
CombinePaths("This", "Is", "A", "Test"),
CombinePaths("This/", @"Is\A", "Test"),
CombinePaths(@"This\Is", @"A\Test"),
CombinePaths(@"This\Is\A\Test"),
CombinePaths("This/", @"Is\", "A", null, "Test", String.Empty),
CombinePaths(null, "This/", "Is", @"A\", "Test")
};
for (int Index = 0; Index < Results.Length; ++Index)
{
var CombinedPath = Results[Index];
Log(CombinedPath);
if (CombinedPath != Results[0])
{
throw new AutomationException("Path {0} does not match path 0: {1} (expected: {2})", Index, CombinedPath, Results[0]);
}
}
}
}
[Help("Tests file utility functions.")]
[Help("file=Filename", "Filename to perform the tests on.")]
class TestFileUtility : BuildCommand
{
public override void ExecuteBuild()
{
string[] DummyFilenames = new string[] { ParseParamValue("file", "") };
DeleteFile(DummyFilenames);
}
}
class TestLog : BuildCommand
{
public override void ExecuteBuild()
{
Log("************************* ShooterGameRun");
string MapToRun = ParseParamValue("map", "/game/maps/sanctuary");
PushDir(CombinePaths(CmdEnv.LocalRoot, @"Engine/Binaries/Win64/"));
// string GameServerExe = CombinePaths(CmdEnv.LocalRoot, @"ShooterGame/Binaries/Win64/ShooterGameServer.exe");
// string GameExe = CombinePaths(CmdEnv.LocalRoot, @"ShooterGame/Binaries/Win64/ShooterGame.exe");
string EditorExe = CombinePaths(CmdEnv.LocalRoot, @"Engine/Binaries/Win64/UE4Editor.exe");
string ServerLogFile = CombinePaths(CmdEnv.LogFolder, "Server.log");
string ServerApp = EditorExe;
string OtherArgs = String.Format("{0} -server -abslog={1} -FORCELOGFLUSH -log", MapToRun, ServerLogFile);
string StartArgs = "ShooterGame ";
string ServerCmdLine = StartArgs + OtherArgs;
ProcessResult ServerProcess = Run(ServerApp, ServerCmdLine, null, ERunOptions.AllowSpew | ERunOptions.NoWaitForExit | ERunOptions.AppMustExist | ERunOptions.NoStdOutRedirect);
LogFileReaderProcess(ServerLogFile, ServerProcess, (string Output) =>
{
if (String.IsNullOrEmpty(Output) == false)
{
Console.Write(Output);
}
return true; // keep reading
});
}
}
[Help("Tests P4 change filetype functionality.")]
[Help("File", "Binary file to change the filetype to writeable")]
[RequireP4]
class TestChangeFileType : BuildCommand
{
public override void ExecuteBuild()
{
var Filename = ParseParamValue("File", "");
if (P4.FStat(Filename).Type == P4FileType.Binary)
{
P4.ChangeFileType(Filename, P4FileAttributes.Writeable);
}
}
}
class TestGamePerf : BuildCommand
{
[DllImport("advapi32.dll", SetLastError = true)]
private static extern bool LogonUser(string UserName, string Domain, string Password, int LogonType, int LogonProvider, out SafeTokenHandle SafeToken);
public override void ExecuteBuild()
{
Log("*********************** TestGamePerf");
string UserName = "test.automation";
string Password = "JJGfh9CX";
string Domain = "epicgames.net";
string FileName = string.Format("UStats_{0:MM-d-yyyy_hh-mm-ss-tt}.csv", DateTime.Now);
string Exe = CombinePaths(CmdEnv.LocalRoot, "Engine", "Binaries", "Win64", "UE4Editor.exe");
string ClientLogFile = "";
string CmdLine = "";
string UStatsPath = "";
string LevelParam = ParseParamValue("level", "");
#region Arguments
switch (LevelParam)
{
case "PerfShooterGame_Santuary":
ClientLogFile = CombinePaths(CmdEnv.LogFolder, "ShooterGame.txt");
UStatsPath = CombinePaths(CmdEnv.LocalRoot, "ShooterGame", "Saved", "Profiling", "UnrealStats");
CmdLine = CombinePaths(CmdEnv.LocalRoot, "ShooterGame", "ShooterGame.uproject ") + CmdEnv.LocalRoot + @"/ShooterGame/Content/Maps/TestMaps/Sanctuary_PerfLoader.umap?game=ffa -game -novsync";
break;
case "PerfShooterGame_Highrise":
ClientLogFile = CombinePaths(CmdEnv.LogFolder, "ShooterGame.txt");
UStatsPath = CombinePaths(CmdEnv.LocalRoot, "ShooterGame", "Saved", "Profiling", "UnrealStats");
CmdLine = CombinePaths(CmdEnv.LocalRoot, "ShooterGame", "ShooterGame.uproject ") + CmdEnv.LocalRoot + @"/ShooterGame/Content/Maps/TestMaps/Highrise_PerfLoader.umap?game=ffa -game -novsync";
break;
case "PerfHoverShip":
ClientLogFile = CombinePaths(CmdEnv.LogFolder, "HoverShip.txt");
UStatsPath = CombinePaths(CmdEnv.LocalRoot, "Samples", "SampleGames", "HoverShip", "Saved", "Profiling", "UnrealStats");
CmdLine = CombinePaths(CmdEnv.LocalRoot, "Samples", "HoverShip", "HoverShip.uproject ") + CmdEnv.LocalRoot + @"/Samples/HoverShip/Content/Maps/HoverShip_01.umap -game -novsync";
break;
case "PerfInfiltrator":
ClientLogFile = CombinePaths(CmdEnv.LogFolder, "Infiltrator.txt");
UStatsPath = CombinePaths(CmdEnv.LocalRoot, "Samples", "Showcases", "Infiltrator", "Saved", "Profiling", "UnrealStats");
CmdLine = CombinePaths(CmdEnv.LocalRoot, "Samples", "Showcases", "Infiltrator", "Infiltrator.uproject ") + CmdEnv.LocalRoot + @"/Samples/Showcases/Infiltrator/Content/Maps/TestMaps/Visuals/VIS_ArtDemo_PerfLoader.umap -game -novsync";
break;
case "PerfElemental":
ClientLogFile = CombinePaths(CmdEnv.LogFolder, "Elemental.txt");
UStatsPath = CombinePaths(CmdEnv.LocalRoot, "Samples", "Showcases", "Elemental", "Saved", "Profiling", "UnrealStats");
CmdLine = CombinePaths(CmdEnv.LocalRoot, "Samples", "Showcases", "Elemental", "Elemental.uproject ") + CmdEnv.LocalRoot + @"/Samples/Showcases/Elemental/Content/Misc/Automated_Perf/Elemental_PerfLoader.umap -game -novsync";
break;
case "PerfStrategyGame":
ClientLogFile = CombinePaths(CmdEnv.LogFolder, "StrategyGame.txt");
UStatsPath = CombinePaths(CmdEnv.LocalRoot, "StrategyGame", "Saved", "Profiling", "UnrealStats");
CmdLine = CombinePaths(CmdEnv.LocalRoot, "StrategyGame", "StrategyGame.uproject ") + CmdEnv.LocalRoot + @"/StrategyGame/Content/Maps/TestMaps/TowerDefenseMap_PerfLoader.umap -game -novsync";
break;
case "PerfVehicleGame":
ClientLogFile = CombinePaths(CmdEnv.LogFolder, "VehicleGame.txt");
UStatsPath = CombinePaths(CmdEnv.LocalRoot, "VehicleGame", "Saved", "Profiling", "UnrealStats");
CmdLine = CombinePaths(CmdEnv.LocalRoot, "VehicleGame", "VehicleGame.uproject ") + CmdEnv.LocalRoot + @"/VehicleGame/Content/Maps/TestMaps/VehicleTrack_PerfLoader.umap -game -novsync";
break;
case "PerfPlatformerGame":
ClientLogFile = CombinePaths(CmdEnv.LogFolder, "PlatformerGame.txt");
UStatsPath = CombinePaths(CmdEnv.LocalRoot, "PlatformerGame", "Saved", "Profiling", "UnrealStats");
CmdLine = CombinePaths(CmdEnv.LocalRoot, "PlatformerGame", "PlatformerGame.uproject ") + CmdEnv.LocalRoot + @"/PlatformerGame/Content/Maps/Platformer_StreetSection.umap -game -novsync";
break;
case "PerfLanderGame":
ClientLogFile = CombinePaths(CmdEnv.LogFolder, "LanderGame.txt");
UStatsPath = CombinePaths(CmdEnv.LocalRoot, "LanderGame", "Saved", "Profiling", "UnrealStats");
CmdLine = CombinePaths(CmdEnv.LocalRoot, "LanderGame", "LanderGame.uproject ") + CmdEnv.LocalRoot + @"/LanderGame/Content/Maps/sphereworld.umap -game -novsync";
break;
case "PerfDemoLets_DynamicLighting":
ClientLogFile = CombinePaths(CmdEnv.LogFolder, "DemoLets_DynamicLighting.txt");
UStatsPath = CombinePaths(CmdEnv.LocalRoot, "Samples", "DemoLets", "Saved", "Profiling", "UnrealStats");
CmdLine = CombinePaths(CmdEnv.LocalRoot, "DemoLets", "DemoLets.uproject ") + CmdEnv.LocalRoot + @"/BasicMaterials/Content/Maps/Performance/Demolet_DynamicLighting_PerfLoader.umap -game -novsync";
break;
case "PerfDemoLets_BasicMaterials":
ClientLogFile = CombinePaths(CmdEnv.LogFolder, "DemoLets_BasicMaterials.txt");
UStatsPath = CombinePaths(CmdEnv.LocalRoot, "Samples", "DemoLets", "Saved", "Profiling", "UnrealStats");
CmdLine = CombinePaths(CmdEnv.LocalRoot, "DemoLets", "DemoLets.uproject ") + CmdEnv.LocalRoot + @"/EffectsGallery/Content/Maps/Performance/BasicMaterials_PerfLoader.umap -game -novsync";
break;
case "PerfDemoLets_DemoRoom":
ClientLogFile = CombinePaths(CmdEnv.LogFolder, "DemoLets_DemoRoom.txt");
UStatsPath = CombinePaths(CmdEnv.LocalRoot, "Samples", "DemoLets", "Saved", "Profiling", "UnrealStats");
CmdLine = CombinePaths(CmdEnv.LocalRoot, "DemoLets", "DemoLets.uproject ") + CmdEnv.LocalRoot + @"/EffectsGallery/Content/Maps/Performance/DemoRoom_Effects_hallway_PerfLoader.umap -game -novsync";
break;
case "PerfDemoLets_Reflections":
ClientLogFile = CombinePaths(CmdEnv.LogFolder, "DemoLets_Reflections.txt");
UStatsPath = CombinePaths(CmdEnv.LocalRoot, "Samples", "DemoLets", "Saved", "Profiling", "UnrealStats");
CmdLine = CombinePaths(CmdEnv.LocalRoot, "DemoLets", "DemoLets.uproject ") + CmdEnv.LocalRoot + @"/EffectsGallery/Content/Maps/Performance/DemoLet_Reflections_PerfLoader.umap -game -novsync";
break;
case "PerfDemoLets_PostProcessing":
ClientLogFile = CombinePaths(CmdEnv.LogFolder, "DemoLets_PostProcessing.txt");
UStatsPath = CombinePaths(CmdEnv.LocalRoot, "Samples", "DemoLets", "Saved", "Profiling", "UnrealStats");
CmdLine = CombinePaths(CmdEnv.LocalRoot, "DemoLets", "DemoLets.uproject ") + CmdEnv.LocalRoot + @"/EffectsGallery/Content/Maps/Performance/Demolet_PostProcessing_PerfLoader.umap -game -novsync";
break;
case "PerfDemoLets_Physics":
ClientLogFile = CombinePaths(CmdEnv.LogFolder, "DemoLets_Physics.txt");
UStatsPath = CombinePaths(CmdEnv.LocalRoot, "Samples", "DemoLets", "Saved", "Profiling", "UnrealStats");
CmdLine = CombinePaths(CmdEnv.LocalRoot, "DemoLets", "DemoLets.uproject ") + CmdEnv.LocalRoot + @"/EffectsGallery/Content/Maps/Performance/Demolet_Physics_PerfLoader.umap -game -novsync";
break;
case "PerfDemoLets_ShadowMaps":
ClientLogFile = CombinePaths(CmdEnv.LogFolder, "DemoLets_ShadowMaps.txt");
UStatsPath = CombinePaths(CmdEnv.LocalRoot, "Samples", "DemoLets", "Saved", "Profiling", "UnrealStats");
CmdLine = CombinePaths(CmdEnv.LocalRoot, "DemoLets", "DemoLets.uproject ") + CmdEnv.LocalRoot + @"/EffectsGallery/Content/Maps/Performance/Demolet_CascadingShadowMaps_PerfLoader.umap -game -novsync";
break;
case "PerfDemoLets_Weather":
ClientLogFile = CombinePaths(CmdEnv.LogFolder, "DemoLets_Weather.txt");
UStatsPath = CombinePaths(CmdEnv.LocalRoot, "Samples", "DemoLets", "Saved", "Profiling", "UnrealStats");
CmdLine = CombinePaths(CmdEnv.LocalRoot, "DemoLets", "DemoLets.uproject ") + CmdEnv.LocalRoot + @"/EffectsGallery/Content/Maps/Performance/dynamic_weather_selector_PerfLoader.umap -game -novsync";
break;
case "PerfRealisticRendering_RoomNight":
ClientLogFile = CombinePaths(CmdEnv.LogFolder, "RealisticRendering_RoomNight.txt");
UStatsPath = CombinePaths(CmdEnv.LocalRoot, "Samples", "ShowCases", "RealisticRendering", "Saved", "Profiling", "UnrealStats");
CmdLine = CombinePaths(CmdEnv.LocalRoot, "Samples", "Showcases", "RealisticRendering", "RealisticRendering.uproject ") + CmdEnv.LocalRoot + @"/RealisticRendering/Content/Maps/Performance/RoomNight_PerfLoader.umap -game -novsync";
break;
case "PerfRealisticRendering_NoLight":
ClientLogFile = CombinePaths(CmdEnv.LogFolder, "RealisticRendering_NoLight.txt");
UStatsPath = CombinePaths(CmdEnv.LocalRoot, "Samples", "ShowCases", "RealisticRendering", "Saved", "Profiling", "UnrealStats");
CmdLine = CombinePaths(CmdEnv.LocalRoot, "Samples", "Showcases", "RealisticRendering", "RealisticRendering.uproject ") + CmdEnv.LocalRoot + @"/RealisticRendering/Content/Maps/Performance/RoomNightNoLights_PerfLoader.umap -game -novsync";
break;
case "PerfRealisticRendering_Room":
ClientLogFile = CombinePaths(CmdEnv.LogFolder, "RealisticRendering_Room.txt");
UStatsPath = CombinePaths(CmdEnv.LocalRoot, "Samples", "ShowCases", "RealisticRendering", "Saved", "Profiling", "UnrealStats");
CmdLine = CombinePaths(CmdEnv.LocalRoot, "Samples", "Showcases", "RealisticRendering", "RealisticRendering.uproject ") + CmdEnv.LocalRoot + @"/RealisticRendering/Content/Maps/Performance/Room_PerfLoader.umap -game -novsync";
break;
case "PerfMorphTargets":
ClientLogFile = CombinePaths(CmdEnv.LogFolder, "MorphTargets.txt");
UStatsPath = CombinePaths(CmdEnv.LocalRoot, "Samples", "ShowCases", "MorphTargets", "Saved", "Profiling", "UnrealStats");
CmdLine = CombinePaths(CmdEnv.LocalRoot, "Samples", "Showcases", "MorphTargets", "MorphTargets.uproject ") + CmdEnv.LocalRoot + @"/MorphTargets/Content/Maps/Performance/MorphTargets_PerfLoader.umap -game -novsync";
break;
case "PerfMatinee":
ClientLogFile = CombinePaths(CmdEnv.LogFolder, "Matinee.txt");
UStatsPath = CombinePaths(CmdEnv.LocalRoot, "Samples", "ShowCases", "PostProcessMatinee", "Saved", "Profiling", "UnrealStats");
CmdLine = CombinePaths(CmdEnv.LocalRoot, "Samples", "Showcases", "PostProcessMatinee", "PostProcessMatinee.uproject ") + CmdEnv.LocalRoot + @"/PostProcessMatinee/Content/Maps/Performance/PostProcessMatinee_PerfLoader.umap -game -novsync";
break;
case "PerfLandScape":
ClientLogFile = CombinePaths(CmdEnv.LogFolder, "LandScape.txt");
UStatsPath = CombinePaths(CmdEnv.LocalRoot, "Samples", "ShowCases", "Landscape_WorldMachine", "Saved", "Profiling", "UnrealStats");
CmdLine = CombinePaths(CmdEnv.LocalRoot, "Samples", "Showcases", "Landscape_WorldMachine", "Landscape_WorldMap.uproject ") + CmdEnv.LocalRoot + @"/Landscape_WorldMachine/Content/Maps/Performance/LandscapeMap_PerfLoader.umap -game -novsync";
break;
case "PerfLandScape_Moutain":
ClientLogFile = CombinePaths(CmdEnv.LogFolder, "LandScape_Moutain.txt");
UStatsPath = CombinePaths(CmdEnv.LocalRoot, "Samples", "ShowCases", "Landscape", "Saved", "Profiling", "UnrealStats");
CmdLine = CombinePaths(CmdEnv.LocalRoot, "Samples", "Showcases", "Landscape", "Landscape.uproject ") + CmdEnv.LocalRoot + @"/Landscape/Content/Maps/Performance/MoutainRange_PerfLoader.umap -game -novsync";
break;
case "PerfMobile":
ClientLogFile = CombinePaths(CmdEnv.LogFolder, "Mobile.txt");
UStatsPath = CombinePaths(CmdEnv.LocalRoot, "Samples", "ShowCases", "Mobile", "Saved", "Profiling", "UnrealStats");
CmdLine = CombinePaths(CmdEnv.LocalRoot, "Samples", "Showcases", "Mobile", "Mobile.uproject ") + CmdEnv.LocalRoot + @"/Mobile/Content/Maps/Performance/Testmap_PerfLoader.umap -game -novsync";
break;
case "PerfSampleGames":
ClientLogFile = CmdEnv.LogFolder;
break;
default:
break;
}
#endregion
try
{
SafeTokenHandle SafeToken;
int LogonInteractive = 2;
int ProviderDefualt = 0;
/*bool bLogonSuccess = */
LogonUser(UserName, Domain, Password, LogonInteractive, ProviderDefualt, out SafeToken);
using (SafeToken)
{
using (WindowsIdentity NewUser = new WindowsIdentity(SafeToken.DangerousGetHandle()))
{
using (WindowsImpersonationContext TestAccount = NewUser.Impersonate())
{
if (LevelParam == "PerfSampleGames")
{
}
else
{
if (!File.Exists(ClientLogFile))
{
Log("Creating log file");
File.Create(ClientLogFile).Close();
Log(ClientLogFile);
Log("Log file created");
}
RunAndLog(Exe, CmdLine, ClientLogFile);
}
DirectoryInfo[] UStatsDirectories = new DirectoryInfo(UStatsPath).GetDirectories();
DirectoryInfo CurrentUStatsDirectory = UStatsDirectories[0];
for (int i = 0; i < UStatsDirectories.Length; i++)
{
if (UStatsDirectories[i].LastWriteTime > CurrentUStatsDirectory.LastWriteTime)
{
CurrentUStatsDirectory = UStatsDirectories[i];
}
}
FileInfo[] UStatsFilePaths = new DirectoryInfo(CurrentUStatsDirectory.FullName).GetFiles();
FileInfo CurrentUStatsFilePath = UStatsFilePaths[0];
for (int i = 0; i < UStatsFilePaths.Length; i++)
{
if (UStatsFilePaths[i].LastWriteTime > CurrentUStatsFilePath.LastWriteTime)
{
CurrentUStatsFilePath = UStatsFilePaths[i];
}
}
try
{
string FrontEndExe = CombinePaths(CmdEnv.LocalRoot, "Engine", "Binaries", "Win64", "UnrealFrontend.exe");
CmdLine = " -run=convert -infile=" + CurrentUStatsFilePath.FullName + @" -outfile=D:\" + FileName + " -statlist=GameThread+RenderThread+StatsThread+STAT_FrameTime+STAT_PhysicalAllocSize+STAT_VirtualAllocSize";
RunAndLog(FrontEndExe, CmdLine, ClientLogFile);
}
catch (Exception Err)
{
Log(Err.Message);
}
}
}
}
}
catch (Exception Err)
{
Log(Err.Message);
}
}
}
public sealed class SafeTokenHandle : SafeHandleZeroOrMinusOneIsInvalid
{
private SafeTokenHandle()
: base(true)
{
}
[DllImport("kernel32.dll")]
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
[SuppressUnmanagedCodeSecurity]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool CloseHandle(IntPtr Handle);
protected override bool ReleaseHandle()
{
return CloseHandle(handle);
}
}
[Help("Tests if UE4Build properly copies all relevent UAT build products to the Binaries folder.")]
public class TestUATBuildProducts : BuildCommand
{
public override void ExecuteBuild()
{
UE4Build Build = new UE4Build(this);
Build.AddUATFilesToBuildProducts();
Log("Build products:");
foreach (var Product in Build.BuildProductFiles)
{
Log(" " + Product);
}
}
}
[Help("Tests WatchdogTimer functionality. The correct result is to exit the application with ExitCode=1 after a few seconds.")]
public class TestWatchdogTimer : BuildCommand
{
public override void ExecuteBuild()
{
Log("Starting 1st timer (1s). This should not crash.");
using (var SafeTimer = new WatchdogTimer(1))
{
// Wait 500ms
Log("Started {0}", SafeTimer.GetProcessName());
Thread.Sleep(500);
}
Log("First timer disposed successfully.");
Log("Starting 2nd timer (2s). This should throw an exception after 1 second.");
try
{
using (var CrashTimer = new WatchdogTimer(2))
{
// Wait 5s (this will trigger the watchdog timer)
Log("Started {0}", CrashTimer.GetProcessName());
Thread.Sleep(1000);
throw new Exception("Test exceptions under WatchdogTimer");
}
}
catch (Exception Ex)
{
Log("Triggered exception guarded by WatchdogTimer:");
Log(Ex.Message);
}
Log("Starting 3rd timer (2s). This should crash after 2 seconds.");
using (var CrashTimer = new WatchdogTimer(2))
{
// Wait 5s (this will trigger the watchdog timer)
Log("Started {0}", CrashTimer.GetProcessName());
Thread.Sleep(5000);
}
}
}
class TestOSSCommands : BuildCommand
{
public override void ExecuteBuild()
{
string Exe = CombinePaths(CmdEnv.LocalRoot, "Engine", "Binaries", "Win64", "UE4Editor.exe");
string CmdLine = "qagame automation-osscommands -game -log -logcmds=" + "\"" + "global none, logonline verbose, loghttp log, LogBlueprintUserMessages log" + "\"";
string ClientLogFile = CombinePaths(CmdEnv.LogFolder, String.Format("QALog_{0:yyyy-MM-dd_hh-mm-ss-tt}.txt", DateTime.Now));
string LogDirectory = CombinePaths(CmdEnv.LocalRoot, "Saved", "Logs");
if (!File.Exists(ClientLogFile))
{
File.Create(ClientLogFile).Close();
}
try
{
RunAndLog(Exe, CmdLine, ClientLogFile);
}
catch (Exception Err)
{
Log(Err.Message);
}
FileInfo[] LogFiles = new DirectoryInfo(LogDirectory).GetFiles();
FileInfo QALogFile = LogFiles[0];
for (int i = 0; i < LogFiles.Length; i++)
{
if (LogFiles[i].LastWriteTime > QALogFile.LastWriteTime)
{
QALogFile = LogFiles[i];
}
}
string[] Lines = File.ReadAllLines(QALogFile.FullName);
StreamWriter WriteErrors = new StreamWriter(new FileStream(ClientLogFile, FileMode.Append, FileAccess.Write, FileShare.ReadWrite));
# region Error List
/*
* online sub=amazon test friends - Warning: Failed to get friends interface for amazon
* online sub=amazon test identity - Error: RegisterUser() : OnlineSubsystemAmazon is improperly configured in DefaultEngine.ini
* ErrorCode 00002EE7 - http test <iterations> <url> - Desc: The server name or address could not be resolved
* Error/code 404 - online sub=mcp test validateaccount <email> <validation code> - Warning: MCP: Mcp validate Epic account request failed. Invalid response. Not found.
* Error/code 404 - online sub=mcp test deleteaccount <email> <password> - Warning: MCP: Mcp delete Epic account request failed. Not found.
* online sub=mcp test friends - Warning: Failed to get friends interface for mcp
* bSucess: 0 - online sub=mcp test sessionhost - LogOnline:Verbose: OnCreateSessionComplete Game bSuccess: 0
* Code 401 - online sub=mcp test time - Failed to query server time
* Error code 500 - online sub=mcp test entitlements - profile template not found
*/
#endregion
using (WriteErrors)
{
for (int i = 0; i < Lines.Length; i++)
{
if (Lines[i].Contains("error=404") || Lines[i].Contains("code=404"))
{
WriteErrors.WriteLine(Lines[i]);
}
else if (Lines[i].Contains("code=401"))
{
WriteErrors.WriteLine(Lines[i]);
}
else if (Lines[i].Contains("error=500"))
{
WriteErrors.WriteLine(Lines[i]);
}
else if (Lines[i].Contains("bSucess: 0"))
{
WriteErrors.WriteLine(Lines[i]);
}
else if (Lines[i].Contains("Warning: Failed to get friends interface"))
{
WriteErrors.WriteLine(Lines[i]);
}
else if (Lines[i].Contains("Error: RegisterUser() : OnlineSubsystemAmazon is improperly configured"))
{
WriteErrors.WriteLine(Lines[i]);
}
else if (Lines[i].Contains("ErrorCode 00002EE7"))
{
WriteErrors.WriteLine(Lines[i]);
}
}
}
}
}
[Help("Builds a project using UBT. Syntax is similar to UBT with the exception of '-', i.e. UBT -QAGame -Development -Win32")]
[Help("NoXGE", "Disable XGE")]
[Help("Clean", "Clean build products before building")]
public class UBT : BuildCommand
{
public override void ExecuteBuild()
{
var Build = new UE4Build(this);
var Agenda = new UE4Build.BuildAgenda();
var Platform = UnrealBuildTool.UnrealTargetPlatform.Win64;
var Configuration = UnrealBuildTool.UnrealTargetConfiguration.Development;
var Targets = new List<string>();
foreach (var ObjParam in Params)
{
var Param = (string)ObjParam;
UnrealBuildTool.UnrealTargetPlatform ParsePlatform;
if (Enum.TryParse<UnrealBuildTool.UnrealTargetPlatform>(Param, true, out ParsePlatform))
{
Platform = ParsePlatform;
continue;
}
UnrealBuildTool.UnrealTargetConfiguration ParseConfiguration;
if (Enum.TryParse<UnrealBuildTool.UnrealTargetConfiguration>(Param, true, out ParseConfiguration))
{
Configuration = ParseConfiguration;
continue;
}
if (String.Compare("NoXGE", Param, true) != 0 && String.Compare("Clean", Param, true) != 0)
{
Targets.Add(Param);
}
}
var Clean = ParseParam("Clean");
Agenda.AddTargets(Targets.ToArray(), Platform, Configuration);
Log("UBT Buid");
Log("Targets={0}", String.Join(",", Targets));
Log("Platform={0}", Platform);
Log("Configuration={0}", Configuration);
Log("Clean={0}", Clean);
Build.Build(Agenda, InUpdateVersionFiles: false);
Log("UBT Completed");
}
}
[Help("Helper command to sync only source code + engine files from P4.")]
[Help("Branch", "Optional branch depot path, default is: -Branch=//depot/UE4")]
[Help("CL", "Optional changelist to sync (default is latest), -CL=1234567")]
[Help("Sync", "Optional list of branch subforlders to always sync separated with '+', default is: -Sync=Engine/Binaries/ThirdParty+Engine/Content")]
[Help("Exclude", "Optional list of folder names to exclude from syncing, separated with '+', default is: -Exclude=Binaries+Content+Documentation+DataTables")]
[RequireP4]
public class SyncSource : BuildCommand
{
private void SafeSync(string SyncCmdLine)
{
try
{
P4.Sync(SyncCmdLine);
}
catch (Exception Ex)
{
LogError("Unable to sync {0}", SyncCmdLine);
LogError(Ex.Message);
}
}
public override void ExecuteBuild()
{
var Changelist = ParseParamValue("cl", "");
if (!String.IsNullOrEmpty(Changelist))
{
Changelist = "@" + Changelist;
}
var AlwaysSync = new List<string>(new string[]
{
"Engine/Binaries/ThirdParty",
"Engine/Content",
}
);
var AdditionalAlwaysSyncPaths = ParseParamValue("sync");
if (!String.IsNullOrEmpty(AdditionalAlwaysSyncPaths))
{
var AdditionalPaths = AdditionalAlwaysSyncPaths.Split(new string[] { "+" }, StringSplitOptions.RemoveEmptyEntries);
AlwaysSync.AddRange(AdditionalPaths);
}
var ExclusionList = new HashSet<string>(new string[]
{
"Binaries",
"Content",
"Documentation",
"DataTables",
},
StringComparer.InvariantCultureIgnoreCase
);
var AdditionalExlusionPaths = ParseParamValue("exclude");
if (!String.IsNullOrEmpty(AdditionalExlusionPaths))
{
var AdditionalPaths = AdditionalExlusionPaths.Split(new string[] { "+" }, StringSplitOptions.RemoveEmptyEntries);
foreach (var Dir in AdditionalPaths)
{
ExclusionList.Add(Dir);
}
}
var DepotPath = ParseParamValue("branch", "//depot/UE4/");
foreach (var AlwaysSyncSubFolder in AlwaysSync)
{
var SyncCmdLine = CombinePaths(PathSeparator.Depot, DepotPath, AlwaysSyncSubFolder, "...") + Changelist;
Log("Syncing {0}", SyncCmdLine, Changelist);
SafeSync(SyncCmdLine);
}
DepotPath = CombinePaths(PathSeparator.Depot, DepotPath, "*");
var ProjectDirectories = P4.Dirs(String.Format("-D {0}", DepotPath));
foreach (var ProjectDir in ProjectDirectories)
{
var ProjectDirPath = CombinePaths(PathSeparator.Depot, ProjectDir, "*");
var SubDirectories = P4.Dirs(ProjectDirPath);
foreach (var SubDir in SubDirectories)
{
var SubDirName = Path.GetFileNameWithoutExtension(GetDirectoryName(SubDir));
if (!ExclusionList.Contains(SubDirName))
{
var SyncCmdLine = CombinePaths(PathSeparator.Depot, SubDir, "...") + Changelist;
Log("Syncing {0}", SyncCmdLine);
SafeSync(SyncCmdLine);
}
}
}
}
}
[Help("Generates automation project based on a template.")]
[Help("project=ShortName", "Short name of the project, i.e. QAGame")]
[Help("path=FullPath", "Absolute path to the project root directory, i.e. C:\\UE4\\QAGame")]
public class GenerateAutomationProject : BuildCommand
{
public override void ExecuteBuild()
{
var ProjectName = ParseParamValue("project");
var ProjectPath = ParseParamValue("path");
{
var CSProjFileTemplate = ReadAllText(CombinePaths(CmdEnv.LocalRoot, "Engine", "Extras", "UnsupportedTools", "AutomationTemplate", "AutomationTemplate.Automation.xml"));
StringBuilder CSProjBuilder = new StringBuilder(CSProjFileTemplate);
CSProjBuilder.Replace("AutomationTemplate", ProjectName);
{
const string ProjectGuidTag = "<ProjectGuid>";
int ProjectGuidStart = CSProjFileTemplate.IndexOf(ProjectGuidTag) + ProjectGuidTag.Length;
int ProjectGuidEnd = CSProjFileTemplate.IndexOf("</ProjectGuid>", ProjectGuidStart);
string OldProjectGuid = CSProjFileTemplate.Substring(ProjectGuidStart, ProjectGuidEnd - ProjectGuidStart);
string NewProjectGuid = Guid.NewGuid().ToString("B");
CSProjBuilder.Replace(OldProjectGuid, NewProjectGuid);
}
{
const string OutputPathTag = "<OutputPath>";
var OutputPath = CombinePaths(CmdEnv.LocalRoot, "Engine", "Binaries", "DotNET", "AutomationScripts");
int PathStart = CSProjFileTemplate.IndexOf(OutputPathTag) + OutputPathTag.Length;
int PathEnd = CSProjFileTemplate.IndexOf("</OutputPath>", PathStart);
string OldOutputPath = CSProjFileTemplate.Substring(PathStart, PathEnd - PathStart);
string NewOutputPath = ConvertSeparators(PathSeparator.Slash, UnrealBuildTool.Utils.MakePathRelativeTo(OutputPath, ProjectPath)) + "/";
CSProjBuilder.Replace(OldOutputPath, NewOutputPath);
}
if (!DirectoryExists(ProjectPath))
{
CreateDirectory(ProjectPath);
}
WriteAllText(CombinePaths(ProjectPath, ProjectName + ".Automation.csproj"), CSProjBuilder.ToString());
}
{
var PropertiesFileTemplate = ReadAllText(CombinePaths(CmdEnv.LocalRoot, "Engine", "Extras", "UnsupportedTools", "AutomationTemplate", "Properties", "AssemblyInfo.cs"));
StringBuilder PropertiesBuilder = new StringBuilder(PropertiesFileTemplate);
PropertiesBuilder.Replace("AutomationTemplate", ProjectName);
const string AssemblyGuidTag = "[assembly: Guid(\"";
int AssemblyGuidStart = PropertiesFileTemplate.IndexOf(AssemblyGuidTag) + AssemblyGuidTag.Length;
int AssemblyGuidEnd = PropertiesFileTemplate.IndexOf("\")]", AssemblyGuidStart);
string OldAssemblyGuid = PropertiesFileTemplate.Substring(AssemblyGuidStart, AssemblyGuidEnd - AssemblyGuidStart);
string NewAssemblyGuid = Guid.NewGuid().ToString("D");
PropertiesBuilder.Replace(OldAssemblyGuid, NewAssemblyGuid);
string PropertiesPath = CombinePaths(ProjectPath, "Properties");
if (!DirectoryExists(PropertiesPath))
{
CreateDirectory(PropertiesPath);
}
WriteAllText(CombinePaths(PropertiesPath, "AssemblyInfo.cs"), PropertiesBuilder.ToString());
}
}
}
class DumpBranch : BuildCommand
{
public override void ExecuteBuild()
{
Log("************************* DumpBranch");
var HostPlatforms = new List<UnrealTargetPlatform>();
HostPlatforms.Add(UnrealTargetPlatform.Win64);
HostPlatforms.Add(UnrealTargetPlatform.Mac);
new BranchInfo(HostPlatforms);
}
}
[Help("Sleeps for 20 seconds and then exits")]
public class DebugSleep : BuildCommand
{
public override void ExecuteBuild()
{
Thread.Sleep(20000);
}
}
[Help("Tests if Mcp configs loaded properly.")]
class TestMcpConfigs : BuildCommand
{
public override void ExecuteBuild()
{
EpicGames.MCP.Config.McpConfigHelper.Find("localhost");
}
}
[Help("Test Blame P4 command.")]
[Help("File", "(Optional) Filename of the file to produce a blame output for")]
[Help("Out", "(Optional) File to save the blame result to.")]
[Help("Timelapse", "If specified, will use Timelapse command instead of Blame")]
[RequireP4]
class TestBlame : BuildCommand
{
public override void ExecuteBuild()
{
var Filename = ParseParamValue("File", "//depot/UE4/Engine/Source/Runtime/PakFile/Public/IPlatformFilePak.h");
var OutFilename = ParseParamValue("Out");
Log("Creating blame file for {0}", Filename);
P4Connection.BlameLineInfo[] Result = null;
if (ParseParam("Timelapse"))
{
Result = P4.Timelapse(Filename);
}
else
{
Result = P4.Blame(Filename);
}
var BlameResult = new StringBuilder();
foreach (var BlameLine in Result)
{
var ResultLine = String.Format("#{0} in {1} by {2}: {3}", BlameLine.Revision.Revision, BlameLine.Revision.Changelist, BlameLine.Revision.User, BlameLine.Line);
Log(ResultLine);
BlameResult.AppendLine(ResultLine);
}
if (String.IsNullOrEmpty(OutFilename) == false)
{
WriteAllText(OutFilename, BlameResult.ToString());
}
}
}
[Help("Test P4 changes.")]
[RequireP4]
[DoesNotNeedP4CL]
class TestChanges : BuildCommand
{
public override void ExecuteBuild()
{
var CommandParam = ParseParamValue("CommandParam", "//depot/UE4-LauncherReleases/*/Source/...@2091742,2091950 //depot/UE4-LauncherReleases/*/Build/...@2091742,2091950");
{
List<P4Connection.ChangeRecord> ChangeRecords;
if (!P4.Changes(out ChangeRecords, CommandParam, true, true, LongComment: true))
{
throw new AutomationException("failed");
}
foreach (var Record in ChangeRecords)
{
Log("{0} {1} {2}", Record.CL, Record.UserEmail, Record.Summary);
}
}
{
List<P4Connection.ChangeRecord> ChangeRecords;
if (!P4.Changes(out ChangeRecords, "-L " + CommandParam, true, true, LongComment: false))
{
throw new AutomationException("failed");
}
foreach (var Record in ChangeRecords)
{
Log("{0} {1} {2}", Record.CL, Record.UserEmail, Record.Summary);
}
}
}
}
[Help("Spawns a process to test if UAT kills it automatically.")]
class TestKillAll : BuildCommand
{
public override void ExecuteBuild()
{
Log("*********************** TestKillAll");
string Exe = CombinePaths(CmdEnv.LocalRoot, "Engine", "Binaries", "Win64", "UE4Editor.exe");
string ClientLogFile = CombinePaths(CmdEnv.LogFolder, "HoverGameRun");
string CmdLine = " ../../../Samples/Sandbox/HoverShip/HoverShip.uproject -game -forcelogflush -log -abslog=" + ClientLogFile;
Run(Exe, CmdLine, null, ERunOptions.AllowSpew | ERunOptions.NoWaitForExit | ERunOptions.AppMustExist | ERunOptions.NoStdOutRedirect);
Thread.Sleep(10000);
}
}
[Help("Tests CleanFormalBuilds.")]
class TestCleanFormalBuilds : BuildCommand
{
public override void ExecuteBuild()
{
Log("*********************** TestCleanFormalBuilds");
var Dir = ParseParamValue("Dir", @"P:\Builds\UE4\PackagedBuilds\++UE4+Release-4.11");
CleanFormalBuilds(Dir, "CL-*");
}
}
[Help("Spawns a process to test if it can be killed.")]
class TestStopProcess : BuildCommand
{
public override void ExecuteBuild()
{
Log("*********************** TestStopProcess");
string Exe = CombinePaths(CmdEnv.LocalRoot, "Engine", "Binaries", "Win64", "UE4Editor.exe");
string ClientLogFile = CombinePaths(CmdEnv.LogFolder, "HoverGameRun");
string CmdLine = " ../../../Samples/Sandbox/HoverShip/HoverShip.uproject -game -forcelogflush -log -ddc=noshared -abslog=" + ClientLogFile;
for (int Attempt = 0; Attempt < 5; ++Attempt)
{
Log("Attempt: {0}", Attempt);
var Proc = Run(Exe, CmdLine, null, ERunOptions.AllowSpew | ERunOptions.NoWaitForExit | ERunOptions.AppMustExist | ERunOptions.NoStdOutRedirect);
Thread.Sleep(10000);
Proc.StopProcess();
}
Log("One final attempt to test KillAll");
Run(Exe, CmdLine, null, ERunOptions.AllowSpew | ERunOptions.NoWaitForExit | ERunOptions.AppMustExist | ERunOptions.NoStdOutRedirect);
Thread.Sleep(10000);
}
}
[Help("Looks through an XGE xml for overlapping obj files")]
[Help("Source", "full path of xml to look at")]
class LookForOverlappingBuildProducts : BuildCommand
{
public override void ExecuteBuild()
{
var SourcePath = ParseParamValue("Source=");
if (String.IsNullOrEmpty(SourcePath))
{
SourcePath = "D:\\UAT_XGE.xml";
}
if (!FileExists_NoExceptions(SourcePath))
{
throw new AutomationException("Source path not found, please use -source=Path");
}
var Objs = new HashSet<string>( StringComparer.InvariantCultureIgnoreCase );
// /Fo&quot;D:\BuildFarm\buildmachine_++depot+UE4\Engine\Intermediate\Build\Win64\UE4Editor\Development\Projects\Module.Projects.cpp.obj&quot;
var FileText = ReadAllText(SourcePath);
string Start = "/Fo&quot;";
string End = "&quot;";
while (true)
{
var Index = FileText.IndexOf(Start);
if (Index >= 0)
{
FileText = FileText.Substring(Index + Start.Length);
Index = FileText.IndexOf(End);
if (Index >= 0)
{
var ObjFile = FileText.Substring(0, Index);
if (Objs.Contains(ObjFile))
{
LogError("Duplicate obj: {0}", ObjFile);
}
else
{
Objs.Add(ObjFile);
}
FileText = FileText.Substring(Index + End.Length);
}
else
{
break;
}
}
else
{
break;
}
}
}
}
[Help("Copies all files from source directory to destination directory using ThreadedCopyFiles")]
[Help("Source", "Source path")]
[Help("Dest", "Destination path")]
[Help("Threads", "Number of threads used to copy files (64 by default)")]
class TestThreadedCopyFiles : BuildCommand
{
public override void ExecuteBuild()
{
var SourcePath = ParseParamValue("Source=");
var DestPath = ParseParamValue("Dest=");
var Threads = ParseParamInt("Threads=", 64);
if (String.IsNullOrEmpty(SourcePath))
{
throw new AutomationException("Source path not specified, please use -source=Path");
}
if (String.IsNullOrEmpty(DestPath))
{
throw new AutomationException("Destination path not specified, please use -dest=Path");
}
if (!DirectoryExists(SourcePath))
{
throw new AutomationException("Source path {0} does not exist", SourcePath);
}
DeleteDirectory(DestPath);
using (var ScopedCopyTimer = new ScopedTimer("ThreadedCopyFiles"))
{
ThreadedCopyFiles(SourcePath, DestPath, Threads);
}
}
}