// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. using EpicGame; using Gauntlet; using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Text.RegularExpressions; namespace EngineTest { public class AutomatedTestConfig : UnrealTestConfiguration { /// /// Set to true if the editor executes all these tests in is own process and PIE /// [AutoParam] public bool UseEditor = false; /// /// Filter or groups of tests to apply /// [AutoParam] public string TestFilter = "Filter:Project+Filter:System"; /// /// Path to write the automation report to /// [AutoParam] public string ReportOutputPath = ""; /// /// Path the report can be found at /// [AutoParam] public string ReportOutputURL = ""; /// /// Used for having the editor and any client communicate /// public string SessionID = Guid.NewGuid().ToString(); public override void ApplyToConfig(UnrealAppConfig AppConfig, UnrealSessionRole ConfigRole, IEnumerable OtherRoles) { base.ApplyToConfig(AppConfig, ConfigRole, OtherRoles); string ReportOutputArg = string.IsNullOrEmpty(ReportOutputPath) ? "" : string.Format("-ReportOutputPath=\"{0}\"", ReportOutputPath); if (UseEditor) { if (ConfigRole.RoleType == UnrealTargetRole.Editor) { AppConfig.CommandLine += string.Format(" -NoWatchdog -stdout -FORCELOGFLUSH -CrashForUAT -unattended -buildmachine {0} -ExecCmds=\"Automation RunTests {1}; Quit;\"", ReportOutputArg, TestFilter); } } else { string HostIP = UnrealHelpers.GetHostIpAddress(); if (ConfigRole.RoleType == UnrealTargetRole.Client) { AppConfig.CommandLine += string.Format("-sessionid={0} -messaging -log -TcpMessagingConnect={1}:6666", SessionID, HostIP); } else if (ConfigRole.RoleType == UnrealTargetRole.Editor) { AppConfig.CommandLine += string.Format("-nullrhi -ExecCmds=\"Automation StartRemoteSession {0};RunTests {1}; Quit\" -TcpMessagingListen={2}:6666 -log {3}", SessionID, TestFilter, HostIP, ReportOutputArg); } } } } /// /// Runs automated tests on a platform /// public class RunAutomatedTests : UnrealTestNode { private int LastAutomationEntryCount = 0; private DateTime LastAutomationEntryTime = DateTime.MinValue; public RunAutomatedTests(Gauntlet.UnrealTestContext InContext) : base(InContext) { } public override AutomatedTestConfig GetConfiguration() { AutomatedTestConfig Config = base.GetConfiguration(); if (Config.UseEditor) { Config.RequireRole(UnrealTargetRole.Editor); } else { Config.RequireRole(UnrealTargetRole.Editor); Config.RequireRole(UnrealTargetRole.Client); } Config.MaxDuration = Context.TestParams.ParseValue("MaxDuration", 3600); return Config; } public override string Name { get { string BaseName = base.Name; var Config = GetConfiguration(); if (!string.IsNullOrEmpty(Config.TestFilter)) { BaseName += " " + Config.TestFilter; } return BaseName; } } public override void TickTest() { const float IdleTimeout = 5 * 60; List ChannelEntries = new List(); //var AppInstance = GetConfiguration().UseEditor ? TestInstance.EditorApp : TestInstance.ClientApps.FirstOrDefault(); var AppInstance = TestInstance.EditorApp; UnrealLogParser Parser = new UnrealLogParser(AppInstance.StdOut); ChannelEntries.AddRange(Parser.GetLogChannels(new string[] { "Automation", "FunctionalTest" }, false)); if (ChannelEntries.Count > LastAutomationEntryCount) { // log new entries so people have something to look at ChannelEntries.Skip(LastAutomationEntryCount).ToList().ForEach(S => Log.Info("{0}", S)); LastAutomationEntryTime = DateTime.Now; LastAutomationEntryCount = ChannelEntries.Count; } else { if (LastAutomationEntryTime == DateTime.MinValue) { LastAutomationEntryTime = DateTime.Now; } double ElapsedTime = (DateTime.Now - LastAutomationEntryTime).TotalSeconds; if (ElapsedTime > IdleTimeout) { Log.Error("No automation activity observed in last {0:0.00} minutes. Aborting test", IdleTimeout / 60); MarkTestComplete(); SetUnrealTestResult(TestResult.TimedOut); } } base.TickTest(); } protected override int GetExitCodeAndReason(UnrealRoleArtifacts InArtifacts, out string ExitReason) { int ExitCode = base.GetExitCodeAndReason(InArtifacts, out ExitReason); if (InArtifacts.SessionRole.RoleType == UnrealTargetRole.Editor) { // if no fatal errors, check test results if (InArtifacts.LogParser.GetFatalError() == null) { AutomationLogParser Parser = new AutomationLogParser(InArtifacts.LogParser); IEnumerable TotalTests = Parser.GetResults(); IEnumerable FailedTests = TotalTests.Where(R => !R.Passed); if (FailedTests.Any()) { ExitReason = string.Format("{0} of {1} tests failed", FailedTests.Count(), TotalTests.Count()); ExitCode = -1; } } } return ExitCode; } /// /// Override the summary report so we can insert a link to our report and the failed tests /// /// protected override string GetTestSummaryHeader() { MarkdownBuilder MB = new MarkdownBuilder(base.GetTestSummaryHeader()); string ReportLink = GetConfiguration().ReportOutputURL; if (string.IsNullOrEmpty(ReportLink) == false) { MB.Paragraph(string.Format("Report: {0}", ReportLink)); } var EditorArtifacts = SessionArtifacts.Where(A => A.SessionRole.RoleType == UnrealTargetRole.Editor).FirstOrDefault(); if (EditorArtifacts != null) { AutomationLogParser Parser = new AutomationLogParser(EditorArtifacts.LogParser); IEnumerable AllTests = Parser.GetResults(); IEnumerable FailedTests = AllTests.Where(R => !R.Passed); MB.Paragraph(string.Format("{0} of {1} tests passed", AllTests.Count() - FailedTests.Count(), AllTests.Count())); if (FailedTests.Count() > 0) { MB.H3("Test Failures"); foreach (AutomationTestResult Result in FailedTests) { MB.H4(string.Format("{0} Failed", Result.DisplayName)); MB.UnorderedList(Result.Events); } } } return MB.ToString(); } /// /// Override the role summary so we can display the actual test failures /// /// /// /// protected override int GetRoleSummary(UnrealRoleArtifacts InArtifacts, out string Summary) { int ExitCode = base.GetRoleSummary(InArtifacts, out Summary); if (InArtifacts.SessionRole.RoleType == UnrealTargetRole.Editor) { } return ExitCode; } /// /// Returns Errors found during tests. By default only fatal errors are considered /// public override IEnumerable GetErrors() { List AllErrors = new List( base.GetErrors() ); foreach (var Artifact in GetArtifactsWithFailures()) { if (Artifact.SessionRole.RoleType == UnrealTargetRole.Editor) { AutomationLogParser Parser = new AutomationLogParser(Artifact.LogParser); AllErrors.AddRange( Parser.GetResults().Where(R => !R.Passed) .SelectMany(R => R.Events .Where(E => E.ToLower().Contains("error")) ) ); } } return AllErrors; } } }