Ejemplo n.º 1
0
        private static bool ShouldOverrideOverrideBuildFailure()
        {
            if ((Flags & Flag.IgnoreErrorsOnArtifactCreation) == Flag.None)
            {
                return(false);
            }

            var success = false;

            if (string.IsNullOrEmpty(TestResults) && string.IsNullOrEmpty(ExpectedBuildArtifact))
            {
                // No test or build artifacts expected then we allow failure
                RunLogger.LogInfo($"[{nameof(ShouldOverrideOverrideBuildFailure)}] No artifacts expected in command line");
            }
            else if (!string.IsNullOrEmpty(TestResults) && !File.Exists(TestResults))
            {
                // No test results have been generated so we allow failure
                RunLogger.LogInfo($"[{nameof(ShouldOverrideOverrideBuildFailure)}] No test results file has been generated");
            }
            else if (!string.IsNullOrEmpty(ExpectedBuildArtifact) &&
                     !Directory.Exists(ExpectedBuildArtifact) &&
                     !File.Exists(ExpectedBuildArtifact))
            {
                // Build artifact path has been set but nothing has been created then we also allow failure
                RunLogger.LogInfo($"[{nameof(ShouldOverrideOverrideBuildFailure)}] Build artifacts file/folder doesn't exist");
            }
            else
            {
                //TODO: Temporary fix for random compilation issue. If flag is set we ignore whatever the parselogfile reports
                RunLogger.LogResultInfo("Errors were found in the log file but these were ignored since the artifacts were still generated and -ignoreErrorsOnArtifactCreation has been set");
                success = true;
            }

            return(success);
        }
Ejemplo n.º 2
0
        static int Main(string[] args)
        {
            var options = new OptionSet
            {
                {
                    "batchmode",
                    "Run Unity in batch mode. This should always be used in conjunction with the other command line arguments, because it ensures no pop-up windows appear and eliminates the need for any human intervention. When an exception occurs during execution of the script code, the Asset server updates fail, or other operations that fail, Unity immediately exits with return code 1. \nNote that in batch mode, Unity sends a minimal version of its log output to the RunLogger However, the Log Files still contain the full log information. Opening a project in batch mode while the Editor has the same project open is not supported; only a single instance of Unity can run at a time.",
                    v => Flags |= Flag.Batchmode
                },
                {
                    "quit",
                    "Quit the Unity Editor after other commands have finished executing. Note that this can cause error messages to be hidden (however, they still appear in the Editor.log file).",
                    v => Flags |= Flag.Quit
                },
                {
                    "nographics",
                    "When running in batch mode, do not initialize the graphics device at all. This makes it possible to run your automated workflows on machines that don’t even have a GPU (automated workflows only work when you have a window in focus, otherwise you can’t send simulated input commands). Please note that -nographics does not allow you to bake GI, since Enlighten requires GPU acceleration.",
                    v => Flags |= Flag.NoGraphics
                },
                {
                    "automated",
                    "This flag enables some extra logging when running tests. Like when a test is started/finished/etc which is helpful for identifying which test is logging something or what the result of the test was if you don't want to parse the testresults file.",
                    v => Flags |= Flag.Automated
                },
                {
                    "silentcrashes",
                    "Don’t display a crash dialog.",
                    v => Flags |= Flag.SilentCrashes
                },
                {
                    "ignoreErrorsOnArtifactCreation",
                    "Workaround to a random compilation issue introduced in 2018.2+ on slower machines. In that you sometimes get random compilation errors but artifacts are still being produced. So this flag will ignore the errors if the testresults or standalone build artifacts has been generated.",
                    v => Flags |= Flag.IgnoreErrorsOnArtifactCreation
                },
                {
                    "warningsaserrors",
                    "Treat warnings as errors.",
                    v => Flags |= Flag.WarningsAsErrors
                },
                {
                    "runtests",
                    "Executes tests in the project",
                    v => Flags |= Flag.RunTests
                },
                {
                    "unityexecutable=",
                    "Path to unity executable that should run this command",
                    v => UnityExecutable = v
                },
                {
                    "expectedexitcode=",
                    "If you for some reason don't expect to get exit code 0 from the run and want to enforce it",
                    v => ExpectedExitCode = int.Parse(v)
                },
                {
                    "projectpath=",
                    "Open the project at the given path.",
                    v => ProjectPath = v
                },
                {
                    "logfile=",
                    "Specify where the Editor or Windows/Linux/OSX standalone log file are written.",
                    v => LogFile = v
                },
                {
                    "cleanedLogFile=",
                    "Logs file that should only contain important messages (warnings, errors, assertions). If this is set and the specified file is not empty after the run the run will be flagged as failed.",
                    v => CleanedLogFile = v
                },
                {
                    "testresults=",
                    "The path indicating where the result file should be saved. The result file is saved in Project’s root folder by default.",
                    v => TestResults = v
                },
                {
                    "scriptingBackend=",
                    "Will hack in the scripting backend for standalone in the ProjectSettings.asset so you can easily toggle it in CI. Valid values are: mono, il2cpp",
                    v => ScriptingBackendOverride = Enum.Parse <ScriptingBackend>(v)
                },
                {
                    "displayResolutionDialog=",
                    "Will hack in if the display resolution dialog should be enabled or not in built players into the ProjectSettings.asset. Valid values are: enabled, disabled",
                    v => DisplayResolutionDialogOverride = Enum.Parse <DisplayResolutionDialog>(v)
                },
                {
                    "buildLinuxUniversalPlayer=",
                    "Build a combined 32-bit and 64-bit standalone Linux player (for example, -buildLinuxUniversalPlayer path/to/your/build).",
                    v => buildLinuxUniversalPlayer = v
                },
                {
                    "buildOSXUniversalPlayer=",
                    "Build a combined 32-bit and 64-bit standalone Mac OSX player (for example, -buildOSXUniversalPlayer path/to/your/build.app).",
                    v => buildOSXUniversalPlayer = v
                },
                {
                    "registryoverride=",
                    "If you for some reason need to use a custom npm registry than the one in the manifest.json (maybe you have a fast one for your build system). Empty string removes the field",
                    v => RegistryOverride = v
                },
                {
                    "buildWindows64Player=",
                    "Build a 64-bit standalone Windows player (for example, -buildWindows64Player path/to/your/build.exe).",
                    v => buildWindows64Player = v
                },
                {
                    "timeout=",
                    "Timeout the execution after the supplied seconds. Will fail run if -timeoutIgnore is not set and it times out",
                    v => ExecutionTimeout = int.Parse(v)
                },
                {
                    "timeoutIgnore",
                    "Indicates that if the execution times out it should not flag it as a failure if everything else is ok",
                    v => Flags |= Flag.TimeoutIgnore
                },
                {
                    "scene=",
                    "Modifies the scene list to build with only the scene listed here. Needs to be the relative path to the file from the project path",
                    v => SceneOverride = v
                },
                {
                    "addPackage=",
                    "Modifies the Packages/manifest.json of the project to include the package specified. Use the format packagename@version. Ex: [email protected]. Command can be repeated for multiple packages",
                    v => AddPackages.Add(v)
                },
                {
                    "buildTarget=",
                    $"Allows the selection of an active build target before loading a project. Possible options are: {string.Join(", ", Enum.GetNames(typeof(BuildTargets)).ToList())}.",
                    v => BuildTarget = v
                }
            };

            try
            {
                ExtraArgs = options.Parse(args);
            }
            catch (OptionException e)
            {
                RunLogger.LogError(e.Message);
                RunLogger.Dump();
                options.WriteOptionDescriptions(Console.Out);
                throw;
            }
            if (ExtraArgs.Any())
            {
                RunLogger.LogInfo($"Unknown commands passed. These will be passed a long to the process:\n {string.Join(" ", ExtraArgs)}");
            }

            if (!IsValidPath("unityexecutable", UnityExecutable))
            {
                RunLogger.Dump();
                options.WriteOptionDescriptions(Console.Out);
                return(-1);
            }
            if (!IsValidPath("projectpath", ProjectPath))
            {
                RunLogger.Dump();
                options.WriteOptionDescriptions(Console.Out);
                return(-1);
            }

            if (string.IsNullOrEmpty(LogFile))
            {
                RunLogger.LogError("logfile must be set");
                RunLogger.Dump();
                options.WriteOptionDescriptions(Console.Out);
                return(-1);
            }

            var result = UpdateProjectSettings(ProjectPath);

            if (result != 0)
            {
                RunLogger.Dump();
                return(-1);
            }

            result = UpdateManifest(ProjectPath, AddPackages);
            if (result != 0)
            {
                RunLogger.Dump();
                return(-1);
            }


            if (!IsValidPath("logfile", new FileInfo(LogFile).Directory.FullName))
            {
                RunLogger.Dump();
                return(-1);
            }


            var sb = new StringBuilder();

            sb.Append($"-logFile \"{Path.GetFullPath(LogFile)}\" ");
            sb.Append($"-projectPath \"{Path.GetFullPath(ProjectPath)}\" ");

            if (!string.IsNullOrEmpty(CleanedLogFile))
            {
                sb.Append($"-cleanedLogFile \"{Path.GetFullPath(CleanedLogFile)}\" ");
            }

            if (ExtraArgs.Any())
            {
                sb.Append(string.Join(" ", ExtraArgs));
                sb.Append(" ");
            }

            if ((Flags & Flag.Batchmode) != Flag.None)
            {
                RunLogger.LogInfo("Batchmode is set");
                sb.Append("-batchmode ");
            }

            if ((Flags & Flag.NoGraphics) != Flag.None)
            {
                RunLogger.LogInfo("Nographics is set");
                sb.Append("-nographics ");
            }

            if ((Flags & Flag.SilentCrashes) != Flag.None)
            {
                RunLogger.LogInfo("silentcrashes is set");
                sb.Append("-silent-crashes ");
            }

            if ((Flags & Flag.WarningsAsErrors) != Flag.None)
            {
                RunLogger.LogInfo("warningsaserrors is set");
            }

            if ((Flags & Flag.TimeoutIgnore) != Flag.None)
            {
                RunLogger.LogInfo("timeoutIgnore is set");
            }

            if ((Flags & Flag.IgnoreErrorsOnArtifactCreation) != Flag.None)
            {
                RunLogger.LogInfo("ignoreErrorsOnArtifactCreation is set");
            }

            if ((Flags & Flag.Automated) != Flag.None)
            {
                RunLogger.LogInfo("automated is set");
                sb.Append("-automated ");
            }

            if (!string.IsNullOrEmpty(BuildTarget))
            {
                if (!Enum.TryParse <BuildTargets>(BuildTarget, out var parsedEnum))
                {
                    RunLogger.LogError($"{BuildTarget} is not a valid buildtarget. It has to be one of:\n{string.Join(", ", Enum.GetNames(typeof(BuildTargets)))}");
                    return(-1);
                }
                sb.Append($"-buildTarget {BuildTarget} ");
            }

            if ((Flags & Flag.RunTests) != Flag.None)
            {
                RunLogger.LogInfo("runtests is set");
                sb.Append("-runTests ");

                if (string.IsNullOrEmpty(TestResults))
                {
                    RunLogger.LogWarning("It is not recommended to set runtests but not testresults. It will not be able to parse the test results as part of the report");
                }
                else
                {
                    sb.Append($"-testResults \"{Path.GetFullPath(TestResults)}\" ");
                }
            }

            if ((Flags & Flag.Quit) != Flag.None)
            {
                RunLogger.LogInfo("Quit is set");
                if ((Flags & Flag.RunTests) != Flag.None)
                {
                    RunLogger.LogWarning("quit and runtests cannot be set at once. Ignoring quit command");
                }
                else
                {
                    sb.Append("-quit ");
                }
            }

            if (!string.IsNullOrEmpty(buildWindows64Player))
            {
                RunLogger.LogInfo("buildWindows64Player is set");
                sb.Append($"-buildWindows64Player {buildWindows64Player} ");
                ExpectedBuildArtifact = $"{ProjectPath}/{buildWindows64Player}";
            }

            if (!string.IsNullOrEmpty(buildLinuxUniversalPlayer))
            {
                RunLogger.LogInfo("buildLinuxUniversalPlayer is set");
                sb.Append($"-buildLinuxUniversalPlayer {buildLinuxUniversalPlayer} ");
                ExpectedBuildArtifact = $"{ProjectPath}/{buildLinuxUniversalPlayer}";
            }

            if (!string.IsNullOrEmpty(buildOSXUniversalPlayer))
            {
                RunLogger.LogInfo("buildOSXUniversalPlayer is set");
                sb.Append($"-buildOSXUniversalPlayer {buildOSXUniversalPlayer} ");
                ExpectedBuildArtifact = $"{ProjectPath}/{buildOSXUniversalPlayer}";
            }

            var stopwatch = new Stopwatch();

            stopwatch.Start();
            var runResult = UnityLauncher.Run(sb.ToString());

            RunLogger.LogResultInfo($"Command execution took: {stopwatch.Elapsed}");
            RestoreProjectSettings(ProjectPath);
            if (runResult == RunResult.FailedToStart)
            {
                RunLogger.Dump();
                return(-1);
            }

            if (!LogParser.Parse())
            {
                runResult = RunResult.Failure;
            }

            if (runResult != RunResult.Success && ShouldOverrideOverrideBuildFailure())
            {
                runResult = RunResult.Success;
            }

            if ((Flags & Flag.RunTests) != Flag.None)
            {
                if (File.Exists(TestResults))
                {
                    RunLogger.LogInfo($"Parsing {TestResults}");
                    if (CheckTestResults.Parse(TestResults) != RunResult.Success)
                    {
                        runResult = RunResult.Failure;
                    }
                }
                else
                {
                    RunLogger.LogError($"Could not find {TestResults}");
                    runResult = RunResult.Failure;
                }
            }


            runResult = ParseCleanedLogFileForErrors(runResult);

            if (runResult != RunResult.Success)
            {
                RunLogger.LogResultError("Run has failed");
                RunLogger.Dump();
                return(-1);
            }

            if (!string.IsNullOrEmpty(ExpectedBuildArtifact) && !(File.Exists(ExpectedBuildArtifact) || Directory.Exists(ExpectedBuildArtifact)))
            {
                RunLogger.LogResultError($"Expected to find {ExpectedBuildArtifact} after the execution but it is missing. Check the log for what could have gone wrong");
                RunLogger.Dump();
                return(-1);
            }

            RunLogger.LogResultInfo("Everything looks good. Run has passed");
            RunLogger.Dump();
            return(0);
        }
Ejemplo n.º 3
0
        private static bool ParseLogFile()
        {
            var success = true;

            RunLogger.LogInfo($"Parsing log located at {Path.GetFileName(Program.LogFile)}");
            while (LogIsLocked())
            {
                Thread.Sleep(1000);
            }

            var grabCallStack     = false;
            var lastIssueWasError = false;

            using (var reader = new StreamReader(Program.LogFile))
            {
                string line;
                while ((line = reader.ReadLine()) != null)
                {
                    if (string.IsNullOrWhiteSpace(line))
                    {
                        continue;
                    }
                    if (line.Contains(": error CS") ||
                        line.Contains(": Internal compiler error:") ||
                        line.StartsWith("UnityException: ") ||
                        line.StartsWith("Exception: ") ||
                        line.StartsWith("Assertion Failed:") ||
                        line.StartsWith("Assertion failed on expression:") ||
                        line.Contains("(Error: ") ||
                        line.EndsWith(": One or more errors occurred.") ||
                        (
                            line.StartsWith("(0,0):") && (line.Contains("Exception:") || line.Contains("One or more errors occurred."))
                        ))
                    {
                        grabCallStack     = true;
                        lastIssueWasError = true;
                        CompilerErrors.Add(line);
                        success = false;
                        continue;
                    }
                    if (line.Contains(": warning CS"))
                    {
                        CompilerWarnings.Add(line);
                        lastIssueWasError = false;
                        grabCallStack     = true;
                        continue;
                    }

                    if (line.Contains("Aborting batchmode due to failure:") ||
                        line.Contains("Scripts have compiler errors."))
                    {
                        success = false;
                        continue;
                    }

                    var firstWord = line.Split(' ', 2)[0];
                    if (firstWord.EndsWith("Exception:"))
                    {
                        grabCallStack     = true;
                        lastIssueWasError = true;
                        CompilerErrors.Add(line);
                        success = false;
                        continue;
                    }

                    if (grabCallStack)
                    {
                        if (line.StartsWith("  at ") ||
                            line.StartsWith("(Filename:") ||
                            line.Contains("   --- End of inner exception stack trace ---") ||
                            line.StartsWith("---> (Inner Exception") ||
                            line.StartsWith("Parameter name: "))
                        {
                            if (lastIssueWasError)
                            {
                                var index = CompilerErrors.Count - 1;
                                CompilerErrors[index] = $"{CompilerErrors[index]}\n{line}";
                            }
                            else
                            {
                                var index = CompilerWarnings.Count - 1;
                                CompilerWarnings[index] = $"{CompilerWarnings[index]}\n{line}";
                            }
                        }
                        else
                        {
                            grabCallStack = false;
                            continue;
                        }
                    }
                }
            }

            if (CompilerWarnings.Any())
            {
                Console.Write("\n");
                RunLogger.LogInfo("Found compiler warnings:");
                var warningsToPrint = CompilerWarnings.Count > 30 ? 30 : CompilerWarnings.Count;
                for (var i = 0; i < warningsToPrint; i++)
                {
                    RunLogger.LogWarning(CompilerWarnings[i]);
                }

                if (warningsToPrint < CompilerWarnings.Count)
                {
                    RunLogger.LogWarning($"Did not print all warnings. Stopped after {warningsToPrint}. There are {CompilerWarnings.Count - warningsToPrint} that have not been printed.");
                }

                if ((Program.Flags & Program.Flag.WarningsAsErrors) != Program.Flag.None)
                {
                    success = false;
                    RunLogger.LogResultError("warningsasserrors has been set so marking the run as failed due to warnings");
                }
                Console.Write("\n");
            }
            if (CompilerErrors.Any())
            {
                Console.Write("\n");
                RunLogger.LogError("Found compiler errors:");
                var errorsToPrint = CompilerErrors.Count > 30 ? 30 : CompilerErrors.Count;
                for (var i = 0; i < errorsToPrint; i++)
                {
                    RunLogger.LogError(CompilerErrors[i]);
                }

                if (errorsToPrint < CompilerErrors.Count)
                {
                    RunLogger.LogError($"Did not print all errors. Stopped after {errorsToPrint}. There are {CompilerErrors.Count - errorsToPrint} that have not been printed.");
                }
                success = false;
                Console.Write("\n");
            }

            if (!success)
            {
                RunLogger.LogResultError($"[Compiler] Found {LogParser.CompilerWarnings.Count} warnings and {LogParser.CompilerErrors.Count} errors");
            }
            else
            {
                RunLogger.LogResultInfo($"[Compiler] Found {LogParser.CompilerWarnings.Count} warnings and {LogParser.CompilerErrors.Count} errors");
            }
            return(success);
        }
        public static RunResult Parse(string resultFileName)
        {
            Failures = new List <FailedTest>();


            CheckTestResults.resultFileName = resultFileName;

            try
            {
                var fstream = new FileStream(resultFileName,
                                             FileMode.Open, FileAccess.Read);
                try
                {
                    XmlDocument xmlDoc = new XmlDocument();
                    xmlDoc.Load(fstream);
                    LoadTestResults(xmlDoc.DocumentElement);
                }
                finally
                {
                    fstream.Close();
                }
            }
            catch (Exception ex)
            {
                RunLogger.LogResultError($"Failed to parse {resultFileName}:\n{ex.Message}");
                return(RunResult.FailedToStart);
            }

            var result = RunResult.Success;

            if (Failures.Count > 0)
            {
                RunLogger.LogError("Test failures found:");

                for (int i = 0; i < Failures.Count; i++)
                {
                    var paddedMessage    = string.Join("\n", Failures[i].Message.Split('\n').Select((l) => $"        {l}").ToList());
                    var paddedStacktrace = string.Join("\n", Failures[i].StackTrace.Split('\n').Select((l) => $"        {l}").ToList());
                    RunLogger.LogError($"  {(i + 1)}: {Failures[i].Name}\n{paddedMessage}\n{paddedStacktrace}\n\n");
                }

                result = RunResult.Failure;
            }

            RunLogger.LogResultInfo("Test results:");
            RunLogger.LogResultInfo($"  Total: {Summary.Total}");
            RunLogger.LogResultInfo($"  Passed: {Summary.Passed}");
            RunLogger.LogResultInfo($"  Failed: {Summary.Failed}");
            RunLogger.LogResultInfo($"  Skipped: {Summary.Skipped}");
            RunLogger.LogResultInfo($"  Inconclusive: {Summary.Inconclusive}");

            if (Summary.Total == 0)
            {
                RunLogger.LogResultInfo("No tests were executed");
                result = RunResult.Failure;
            }

            var actualCount = Summary.Passed +
                              Summary.Failed +
                              Summary.Skipped +
                              Summary.Inconclusive;

            if (Summary.Total != actualCount)
            {
                RunLogger.LogResultError($"Test result sums don't match. Total was reported as {Summary.Total} but the numbers add up to {actualCount}");
                result = RunResult.Failure;
            }

            return(result);
        }
Ejemplo n.º 5
0
        private static ProcessResult CheckForCleanupEntry(Process process)
        {
            var fs = new FileStream(Program.LogFile, FileMode.OpenOrCreate, FileAccess.Read, FileShare.ReadWrite);
            var timeoutStopwatch = new Stopwatch();

            timeoutStopwatch.Start();
            using (var stream = new StreamReader(fs))
            {
                var waitingForDeath        = false;
                var waitingForDeathCounter = 10;
                var failureMessagePrinted  = false;
                var timeoutMessagePrinted  = false;
                while (true)
                {
                    var line = stream.ReadLine();
                    if (line != null)
                    {
                        StashLine(line);
                    }
                    else
                    {
                        // Let's chill if there is nothing new
                        Thread.Sleep(10);
                    }
                    if (IsFailureMessage(line))
                    {
                        failureMessagePrinted = true;
                        RunLogger.LogError($"Failure message in the log: {line}");
                    }

                    if (IsInstabilityMessage(line))
                    {
                        timeoutMessagePrinted = true;
                        RunLogger.LogError($"Instability message in the log: {line}");
                    }

                    if (IsExitMessage(line))
                    {
                        RunLogger.LogInfo("Found editor shutdown log print. Waiting 10 seconds for process to quit");
                        waitingForDeath = true;
                    }
                    if (waitingForDeath)
                    {
                        Thread.Sleep(1000);
                        if (waitingForDeathCounter-- <= 0)
                        {
                            if (timeoutMessagePrinted)
                            {
                                // Hopefully temporary hack to work around potential packman timeout issues.
                                // So if we detect a timeout error in the log then we may want to just retry the entire run
                                RunLogger.LogError("Editor did not quit after 10 seconds, but was also timed out. Forcibly quitting and retrying");
                                process.Kill();
                                return(ProcessResult.Timeout);
                            }
                            RunLogger.LogInfo("Editor did not quit after 10 seconds. Forcibly quitting and whitelisting the exit code");
                            process.Kill();
                            return(ProcessResult.IgnoreExitCode);
                        }
                    }
                    else if (Program.ExecutionTimeout.HasValue && timeoutStopwatch.ElapsedMilliseconds > Program.ExecutionTimeout.Value * 1000)
                    {
                        if ((Program.Flags & Program.Flag.TimeoutIgnore) != Program.Flag.None)
                        {
                            RunLogger.LogResultInfo($"Execution timed out after {Program.ExecutionTimeout.Value} seconds");
                            process.Kill();
                            return(ProcessResult.IgnoreExitCode);
                        }

                        RunLogger.LogResultError($"Execution timed out after {Program.ExecutionTimeout.Value} seconds. Failing run");

                        process.Kill();
                        return(ProcessResult.FailedRun);
                    }

                    if (process.HasExited)
                    {
                        while ((line = stream.ReadLine()) != null)
                        {
                            StashLine(line);
                            if (IsInstabilityMessage(line))
                            {
                                timeoutMessagePrinted = true;
                                RunLogger.LogError($"Instability message in the log: {line}");
                            }
                            if (IsFailureMessage(line))
                            {
                                failureMessagePrinted = true;
                            }
                            if (!IsExitMessage(line))
                            {
                                continue;
                            }

                            if (timeoutMessagePrinted)
                            {
                                continue;
                            }

                            if (failureMessagePrinted)
                            {
                                continue;
                            }
                            RunLogger.LogInfo("Unity has exited cleanly.");
                            return(ProcessResult.UseExitCode);
                        }

                        if (timeoutMessagePrinted)
                        {
                            // Hopefully temporary hack to work around potential packman timeout issues.
                            // So if we detect a timeout error in the log then we may want to just retry the entire run
                            RunLogger.LogError("The unity process has exited, but a timeout message was found, flagging run as timed out.");
                            return(ProcessResult.Timeout);
                        }
                        if (waitingForDeath)
                        {
                            RunLogger.LogInfo("Unity has exited cleanly.");
                            return(ProcessResult.UseExitCode);
                        }


                        if (failureMessagePrinted)
                        {
                            RunLogger.LogResultError("The unity process has exited, but a log failure message was detected, flagging run as failed.");
                            return(ProcessResult.FailedRun);
                        }
                        var writer = new StringWriter();
                        writer.WriteLine($"The unity process has exited, but did not print the proper cleanup, did it crash? Marking as failed. The last {LinesToSave} lines of the log was:");
                        foreach (var entry in _lastLines)
                        {
                            writer.WriteLine($"  {entry}");
                        }
                        RunLogger.LogResultError(writer.ToString());
                        return(ProcessResult.FailedRun);
                    }
                }
            }
        }
Ejemplo n.º 6
0
        static int Main(string[] args)
        {
            var options = new OptionSet
            {
                {
                    "batchmode",
                    "Run Unity in batch mode. This should always be used in conjunction with the other command line arguments, because it ensures no pop-up windows appear and eliminates the need for any human intervention. When an exception occurs during execution of the script code, the Asset server updates fail, or other operations that fail, Unity immediately exits with return code 1. \nNote that in batch mode, Unity sends a minimal version of its log output to the RunLogger However, the Log Files still contain the full log information. Opening a project in batch mode while the Editor has the same project open is not supported; only a single instance of Unity can run at a time.",
                    v => Flags |= Flag.Batchmode
                },
                {
                    "nographics",
                    "When running in batch mode, do not initialize the graphics device at all. This makes it possible to run your automated workflows on machines that don’t even have a GPU (automated workflows only work when you have a window in focus, otherwise you can’t send simulated input commands). Please note that -nographics does not allow you to bake GI, since Enlighten requires GPU acceleration.",
                    v => Flags |= Flag.NoGraphics
                },
                {
                    "executable=",
                    "Path to unity executable that should run this command",
                    v => Executable = v
                },
                {
                    "expectedexitcode=",
                    "If you for some reason don't expect to get exit code 0 from the run and want to enforce it",
                    v => ExpectedExitCode = int.Parse(v)
                },
                {
                    "logfile=",
                    "Specify where the Editor or Windows/Linux/OSX standalone log file are written.",
                    v => LogFile = v
                },
                {
                    "cleanedLogFile=",
                    "Logs file that should only contain important messages (warnings, errors, assertions). If this is set and the specified file is not empty after the run the run will be flagged as failed.",
                    v => CleanedLogFile = v
                },
                {
                    "enforceEmptyCleanedLogFile",
                    "If this flag is set the run will fail if anything at all is logged to the cleanedLogFile which is useful if you know the player should never log if everything is running fine",
                    v => Flags |= Flag.EnforceEmptyCleanedLogFile
                },
                {
                    "timeout=",
                    "Timeout the execution after the supplied seconds. Will fail run if -timeoutIgnore is not set and it times out",
                    v => ExecutionTimeout = int.Parse(v)
                },
                {
                    "timeoutIgnore",
                    "Indicates that if the execution times out it should not flag it as a failure if everything else is ok",
                    v => Flags |= Flag.TimeoutIgnore
                },
                {
                    "screenheight=",
                    "Sets the height of the window",
                    v => ScreenHeight = int.Parse(v)
                },
                {
                    "screenwidth=",
                    "Sets the width of the window",
                    v => ScreenWidth = int.Parse(v)
                },
                {
                    "screenquality=",
                    "Sets the render quality. Should be the name of the quality level",
                    v => ScreenQuality = v
                },
            };

            try
            {
                ExtraArgs = options.Parse(args);
            }
            catch (OptionException e)
            {
                RunLogger.LogError(e.Message);
                RunLogger.Dump();
                options.WriteOptionDescriptions(Console.Out);
                throw;
            }

            if (ExtraArgs.Any())
            {
                RunLogger.LogInfo($"Unknown commands passed. These will be passed a long to the process:\n {string.Join(" ", ExtraArgs)}");
            }

            if (!IsValidPath("executable", Executable))
            {
                RunLogger.Dump();
                options.WriteOptionDescriptions(Console.Out);
                return(-1);
            }
            if (Executable.EndsWith(".app"))
            {
                var fileName = Path.GetFileNameWithoutExtension(Executable);
                Executable += $"/Contents/MacOS/{fileName}";
            }

            if (string.IsNullOrEmpty(LogFile))
            {
                RunLogger.LogError("logfile must be set");
                RunLogger.Dump();
                options.WriteOptionDescriptions(Console.Out);
                return(-1);
            }

            if (!IsValidPath("logfile", new FileInfo(LogFile).Directory.FullName))
            {
                RunLogger.Dump();
                options.WriteOptionDescriptions(Console.Out);
                return(-1);
            }

            var sb = new StringBuilder();

            sb.Append($"-logFile \"{Path.GetFullPath(LogFile)}\" ");

            if (!string.IsNullOrEmpty(CleanedLogFile))
            {
                sb.Append($"-cleanedLogFile \"{Path.GetFullPath(CleanedLogFile)}\" ");
            }

            if (ExtraArgs.Any())
            {
                sb.Append(string.Join(" ", ExtraArgs));
                sb.Append(" ");
            }

            if (ExpectedExitCode != 0)
            {
                RunLogger.LogInfo($"Expected exit code or the run will fail: {ExpectedExitCode}");
            }

            if ((Flags & Flag.Batchmode) != Flag.None)
            {
                RunLogger.LogInfo("Batchmode is set");
                sb.Append("-batchmode ");
            }

            if ((Flags & Flag.NoGraphics) != Flag.None)
            {
                RunLogger.LogInfo("Nographics is set");
                sb.Append("-nographics ");
            }

            if ((Flags & Flag.TimeoutIgnore) != Flag.None)
            {
                RunLogger.LogInfo("timeoutIgnore is set");
            }

            if (ScreenHeight.HasValue)
            {
                sb.Append($"-screen-height {ScreenHeight} ");
            }

            if (ScreenWidth.HasValue)
            {
                sb.Append($"-screen-width {ScreenWidth} ");
            }

            if (!string.IsNullOrEmpty(ScreenQuality))
            {
                sb.Append($"-screen-quality {ScreenQuality} ");
            }

            if ((Flags & Flag.EnforceEmptyCleanedLogFile) != Flag.None)
            {
                RunLogger.LogInfo("enforceEmptyCleanedLogFile has been set. Will fail run if cleanedLogFile contents is not empty after run");
            }

            var stopwatch = new Stopwatch();

            stopwatch.Start();
            var runResult = PlayerLauncher.Run(sb.ToString());

            RunLogger.LogResultInfo($"Command execution took: {stopwatch.Elapsed}");
            if (runResult == RunResult.FailedToStart)
            {
                return(-1);
            }
            //if (!LogParser.Parse())
            //    runResult = RunResult.Failure;

            runResult = ParsedCleanedLogFileForErrors(runResult);

            if (runResult != RunResult.Success)
            {
                RunLogger.LogResultError("Run has failed");
                RunLogger.Dump();
                return(-1);
            }

            RunLogger.LogResultInfo("Everything looks good. Run has passed");
            RunLogger.Dump();
            return(0);
        }
Ejemplo n.º 7
0
        private static ProcessResult CheckForCleanupEntry(Process process)
        {
            var fs = new FileStream(Program.LogFile, FileMode.OpenOrCreate, FileAccess.Read, FileShare.ReadWrite);
            var timeoutStopwatch = new Stopwatch();

            timeoutStopwatch.Start();
            using (var stream = new StreamReader(fs))
            {
                var waitingForDeath       = false;
                var failureMessagePrinted = false;
                while (true)
                {
                    var line = stream.ReadLine();

                    if (line == null)
                    {
                        // Let's chill if there is nothing new
                        Thread.Sleep(10);
                    }

                    if (IsFailureMessage(line))
                    {
                        failureMessagePrinted = true;
                        RunLogger.LogError(line);
                    }

                    if (IsExitMessage(line))
                    {
                        RunLogger.LogInfo("Found shutdown log print. Waiting 10 seconds for process to quit");
                        waitingForDeath = true;
                    }
                    else if (Program.ExecutionTimeout.HasValue && timeoutStopwatch.ElapsedMilliseconds > Program.ExecutionTimeout.Value * 1000)
                    {
                        if ((Program.Flags & Program.Flag.TimeoutIgnore) != Program.Flag.None)
                        {
                            RunLogger.LogResultInfo($"Execution timed out after {Program.ExecutionTimeout.Value} seconds");
                            process.Kill();
                            return(ProcessResult.IgnoreExitCode);
                        }

                        RunLogger.LogResultError($"Execution timed out after {Program.ExecutionTimeout.Value} seconds. Failing run");

                        process.Kill();
                        return(ProcessResult.FailedRun);
                    }

                    if (process.HasExited)
                    {
                        if (waitingForDeath)
                        {
                            RunLogger.LogInfo("Player has exited cleanly.");
                            return(ProcessResult.UseExitCode);
                        }

                        while ((line = stream.ReadLine()) != null)
                        {
                            if (IsFailureMessage(line))
                            {
                                failureMessagePrinted = true;
                            }
                            if (!IsExitMessage(line))
                            {
                                continue;
                            }
                            RunLogger.LogInfo("Player has exited cleanly.");
                            return(ProcessResult.UseExitCode);
                        }

                        if (failureMessagePrinted)
                        {
                            RunLogger.LogResultError("The process has exited, but a log failure message was detected, flagging run as failed.");
                            return(ProcessResult.FailedRun);
                        }

                        return(ProcessResult.UseExitCode);
                    }
                }
            }
        }