/// <summary> /// Returns a hash that represents the results of a role. Should be 0 if no fatal errors or ensures /// </summary> /// <param name="InArtifacts"></param> /// <returns></returns> protected virtual string GetRoleResultHash(UnrealRoleArtifacts InArtifacts) { const int MaxCallstackLines = 10; UnrealLogParser.LogSummary LogSummary = InArtifacts.LogSummary; string TotalString = ""; //Func<int, string> ComputeHash = (string Str) => { return Hasher.ComputeHash(Encoding.UTF8.GetBytes(Str)); }; if (LogSummary.FatalError != null) { TotalString += string.Join("\n", InArtifacts.LogSummary.FatalError.Callstack.Take(MaxCallstackLines)); TotalString += "\n"; } foreach (var Ensure in LogSummary.Ensures) { TotalString += string.Join("\n", Ensure.Callstack.Take(MaxCallstackLines)); TotalString += "\n"; } string Hash = Hasher.ComputeHash(TotalString); return(Hash); }
/// <summary> /// Retrieves and saves all artifacts from the provided session role. Artifacts are saved to the destination path /// </summary> /// <param name="InContext"></param> /// <param name="InRunningRole"></param> /// <param name="InDestArtifactPath"></param> /// <returns></returns> public UnrealRoleArtifacts SaveRoleArtifacts(UnrealTestContext InContext, UnrealSessionInstance.RoleInstance InRunningRole, string InDestArtifactPath) { bool IsServer = InRunningRole.Role.RoleType.IsServer(); string RoleName = (InRunningRole.Role.IsDummy() ? "Dummy" : "") + InRunningRole.Role.RoleType.ToString(); UnrealTargetPlatform?Platform = InRunningRole.Role.Platform; string RoleConfig = InRunningRole.Role.Configuration.ToString(); Directory.CreateDirectory(InDestArtifactPath); // Don't archive editor data, there can be a *lot* of stuff in that saved folder! bool IsEditor = InRunningRole.Role.RoleType.UsesEditor(); bool IsDevBuild = InContext.TestParams.ParseParam("dev"); string DestSavedDir = Path.Combine(InDestArtifactPath, "Saved"); string SourceSavedDir = ""; // save the contents of the saved directory SourceSavedDir = InRunningRole.AppInstance.ArtifactPath; // save the output from TTY string ArtifactLogPath = Path.Combine(InDestArtifactPath, RoleName + "Output.log"); // Write a brief Gauntlet header to aid debugging StringBuilder LogOut = new StringBuilder(); LogOut.AppendLine("------ Gauntlet Test ------"); LogOut.AppendFormat("Role: {0}\r\n", InRunningRole.Role); LogOut.AppendFormat("Automation Command: {0}\r\n", Environment.CommandLine); LogOut.AppendLine("---------------------------"); // Write instance stdout stream LogOut.Append(InRunningRole.AppInstance.StdOut); File.WriteAllText(ArtifactLogPath, LogOut.ToString()); Log.Info("Wrote Log to {0}", ArtifactLogPath); if (IsServer == false) { // gif-ify and jpeg-ify any screenshots try { string ScreenshotPath = Path.Combine(SourceSavedDir, "Screenshots", Platform.ToString()).ToLower(); if (Directory.Exists(ScreenshotPath) && Directory.GetFiles(ScreenshotPath).Length > 0) { Log.Info("Downsizing and gifying session images at {0}", ScreenshotPath); // downsize first so gif-step is quicker and takes less resoruces. Utils.Image.ConvertImages(ScreenshotPath, ScreenshotPath, "jpg", true); string GifPath = Path.Combine(InDestArtifactPath, RoleName + "Test.gif"); if (Utils.Image.SaveImagesAsGif(ScreenshotPath, GifPath)) { Log.Info("Saved gif to {0}", GifPath); } } } catch (Exception Ex) { Log.Info("Failed to downsize and gif-ify images! {0}", Ex); } } // don't archive data in dev mode, because peoples saved data could be huuuuuuuge! if (IsEditor == false) { LogLevel OldLevel = Log.Level; Log.Level = LogLevel.Normal; if (Directory.Exists(SourceSavedDir)) { Utils.SystemHelpers.CopyDirectory(SourceSavedDir, DestSavedDir); Log.Info("Archived artifacts to to {0}", DestSavedDir); } else { Log.Info("Archive path '{0}' was not found!", SourceSavedDir); } Log.Level = OldLevel; } else { if (IsEditor) { Log.Info("Skipping archival of assets for editor {0}", RoleName); } else if (IsDevBuild) { Log.Info("Skipping archival of assets for dev build"); } } foreach (EIntendedBaseCopyDirectory ArtifactDir in InRunningRole.Role.AdditionalArtifactDirectories) { if (InRunningRole.AppInstance.Device.GetPlatformDirectoryMappings().ContainsKey(ArtifactDir)) { string SourcePath = InRunningRole.AppInstance.Device.GetPlatformDirectoryMappings()[ArtifactDir]; var DirToCopy = new DirectoryInfo(SourcePath); if (DirToCopy.Exists) { // Grab the final dir name to copy everything into so everything's not just going into root artifact dir. string IntendedCopyLocation = Path.Combine(InDestArtifactPath, DirToCopy.Name); Utils.SystemHelpers.CopyDirectory(SourcePath, IntendedCopyLocation); } } } // TODO REMOVEME- this should go elsewhere, likely a util that can be called or inserted by relevant test nodes. if (IsServer == false) { // Copy over PSOs try { if (InContext.Options.LogPSO) { foreach (var ThisFile in CommandUtils.FindFiles_NoExceptions(true, "*.rec.upipelinecache", true, DestSavedDir)) { bool Copied = false; var JustFile = Path.GetFileName(ThisFile); if (JustFile.StartsWith("++")) { var Parts = JustFile.Split(new Char[] { '+', '-' }).Where(A => A != "").ToArray(); if (Parts.Count() >= 2) { string ProjectName = Parts[0].ToString(); string BuildRoot = CommandUtils.CombinePaths(CommandUtils.RootBuildStorageDirectory()); string SrcBuildPath = CommandUtils.CombinePaths(BuildRoot, ProjectName); string SrcBuildPath2 = CommandUtils.CombinePaths(BuildRoot, ProjectName.Replace("Game", "").Replace("game", "")); if (!CommandUtils.DirectoryExists(SrcBuildPath)) { SrcBuildPath = SrcBuildPath2; } if (CommandUtils.DirectoryExists(SrcBuildPath)) { var JustBuildFolder = JustFile.Replace("-" + Parts.Last(), ""); string PlatformStr = Platform.ToString(); string SrcCLMetaPath = CommandUtils.CombinePaths(SrcBuildPath, JustBuildFolder, PlatformStr, "MetaData"); if (CommandUtils.DirectoryExists(SrcCLMetaPath)) { string SrcCLMetaPathCollected = CommandUtils.CombinePaths(SrcCLMetaPath, "CollectedPSOs"); if (!CommandUtils.DirectoryExists(SrcCLMetaPathCollected)) { Log.Info("Creating Directory {0}", SrcCLMetaPathCollected); CommandUtils.CreateDirectory(SrcCLMetaPathCollected); } if (CommandUtils.DirectoryExists(SrcCLMetaPathCollected)) { string DestFile = CommandUtils.CombinePaths(SrcCLMetaPathCollected, JustFile); CommandUtils.CopyFile_NoExceptions(ThisFile, DestFile, true); if (CommandUtils.FileExists(true, DestFile)) { Log.Info("Deleting local file, copied to {0}", DestFile); CommandUtils.DeleteFile_NoExceptions(ThisFile, true); Copied = true; } } } } } } if (!Copied) { Log.Warning("Could not find anywhere to put this file {0}", JustFile); } } } } catch (Exception Ex) { Log.Info("Failed to copy upipelinecaches to the network {0}", Ex); } } // END REMOVEME UnrealLogParser LogParser = new UnrealLogParser(InRunningRole.AppInstance.StdOut); int ExitCode = InRunningRole.AppInstance.ExitCode; LogParser.GetTestExitCode(out ExitCode); UnrealRoleArtifacts Artifacts = new UnrealRoleArtifacts(InRunningRole.Role, InRunningRole.AppInstance, InDestArtifactPath, ArtifactLogPath, LogParser); return(Artifacts); }
/// <summary> /// Parses the output of an application to try and determine a failure cause (if one exists). Returns /// 0 for graceful shutdown /// </summary> /// <param name="Prefix"></param> /// <param name="App"></param> /// <returns></returns> protected virtual int GetRoleSummary(UnrealRoleArtifacts InArtifacts, out string Summary) { const int MaxLogLines = 10; const int MaxCallstackLines = 20; UnrealLogParser.LogSummary LogSummary = InArtifacts.LogSummary; string ExitReason = "Unknown"; int ExitCode = GetExitCodeAndReason(InArtifacts, out ExitReason); MarkdownBuilder MB = new MarkdownBuilder(); MB.H3(string.Format("Role: {0} ({1} {2})", InArtifacts.SessionRole.RoleType, InArtifacts.SessionRole.Platform, InArtifacts.SessionRole.Configuration)); if (ExitCode != 0) { MB.H4(string.Format("Result: Abnormal Exit: {0}", ExitReason)); } else { MB.H4(string.Format("Result: {0}", ExitReason)); } int FatalErrors = LogSummary.FatalError != null ? 1 : 0; if (LogSummary.FatalError != null) { MB.H4(string.Format("Fatal Error: {0}", LogSummary.FatalError.Message)); MB.UnorderedList(InArtifacts.LogSummary.FatalError.Callstack.Take(MaxCallstackLines)); if (InArtifacts.LogSummary.FatalError.Callstack.Count() > MaxCallstackLines) { MB.Paragraph("See log for full callstack"); } } if (LogSummary.Ensures.Count() > 0) { foreach (var Ensure in LogSummary.Ensures) { MB.H4(string.Format("Ensure: {0}", Ensure.Message)); MB.UnorderedList(Ensure.Callstack.Take(MaxCallstackLines)); if (Ensure.Callstack.Count() > MaxCallstackLines) { MB.Paragraph("See log for full callstack"); } } } MB.Paragraph(string.Format("FatalErrors: {0}, Ensures: {1}, Errors: {2}, Warnings: {3}", FatalErrors, LogSummary.Ensures.Count(), LogSummary.Errors.Count(), LogSummary.Warnings.Count())); if (GetConfiguration().ShowErrorsInSummary&& InArtifacts.LogSummary.Errors.Count() > 0) { MB.H4("Errors"); MB.UnorderedList(LogSummary.Errors.Take(MaxLogLines)); if (LogSummary.Errors.Count() > MaxLogLines) { MB.Paragraph(string.Format("(First {0} of {1} errors)", MaxLogLines, LogSummary.Errors.Count())); } } if (GetConfiguration().ShowWarningsInSummary&& InArtifacts.LogSummary.Warnings.Count() > 0) { MB.H4("Warnings"); MB.UnorderedList(LogSummary.Warnings.Take(MaxLogLines)); if (LogSummary.Warnings.Count() > MaxLogLines) { MB.Paragraph(string.Format("(First {0} of {1} warnings)", MaxLogLines, LogSummary.Warnings.Count())); } } MB.H4("Artifacts"); MB.Paragraph(string.Format("Log: {0}", InArtifacts.LogPath)); MB.Paragraph(string.Format("Commandline: {0}", InArtifacts.AppInstance.CommandLine)); MB.Paragraph(InArtifacts.ArtifactPath); Summary = MB.ToString(); return(ExitCode); }
/// <summary> /// Parses the provided artifacts to determine the cause of an exit and whether it was abnormal /// </summary> /// <param name="InArtifacts"></param> /// <param name="Reason"></param> /// <param name="WasAbnormal"></param> /// <returns></returns> protected virtual int GetExitCodeAndReason(UnrealRoleArtifacts InArtifacts, out string ExitReason) { UnrealLogParser.LogSummary LogSummary = InArtifacts.LogSummary; // Assume failure! int ExitCode = -1; ExitReason = "Unknown"; if (LogSummary.FatalError != null) { ExitReason = "Process encountered fatal error"; } else if (LogSummary.Ensures.Count() > 0 && CachedConfig.FailOnEnsures) { ExitReason = string.Format("Process encountered {0} Ensures", LogSummary.Ensures.Count()); } else if (InArtifacts.AppInstance.WasKilled) { ExitReason = "Process was killed"; } else if (LogSummary.HasTestExitCode) { if (LogSummary.TestExitCode == 0) { ExitReason = "Process exited with code 0"; } else { ExitReason = string.Format("Process exited with error code {0}", LogSummary.TestExitCode); } ExitCode = LogSummary.TestExitCode; } else if (LogSummary.RequestedExit) { ExitReason = string.Format("Process requested exit with no fatal errors"); ExitCode = 0; } else { // ok, process appears to have exited for no good reason so try to divine a result... if (LogSummary.HasTestExitCode == false && InArtifacts.SessionRole.CommandLine.ToLower().Contains("-gauntlet")) { Log.Verbose("Role {0} had 0 exit code but used Gauntlet and no TestExitCode was found. Assuming failure", InArtifacts.SessionRole.RoleType); ExitCode = -1; ExitReason = "No test result from Gauntlet controller"; } } // Normal exits from server are not ok if we had clients running! if (ExitCode == 0 && InArtifacts.SessionRole.RoleType.IsServer()) { bool ClientsKilled = SessionArtifacts.Any(A => A.AppInstance.WasKilled && A.SessionRole.RoleType.IsClient()); if (ClientsKilled) { ExitCode = -1; ExitReason = "Server exited while clients were running"; } } if (ExitCode == -1 && string.IsNullOrEmpty(ExitReason)) { ExitReason = "Process exited with no indication of success"; } return(ExitCode); }