File: TestUtilities.cs
Web Access
Project: src\src\Test\Perf\Utilities\Roslyn.Test.Performance.Utilities.csproj (Roslyn.Test.Performance.Utilities)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
#nullable disable
 
using System;
using System.Diagnostics;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
 
namespace Roslyn.Test.Performance.Utilities
{
    /// <summary>
    /// Global statics shared between the runner and tests.
    /// </summary>
    public static class RuntimeSettings
    {
        /// <summary>
        /// Used as a pseudo-return value for tests to send test objects 
        /// back to the runner.
        /// </summary>
        public static PerfTest[] ResultTests = null;
        /// <summary>
        /// The logger that is being used by the process.
        /// </summary>
        public static ILogger Logger = new ConsoleAndFileLogger();
        /// <summary>
        /// True if the logger should be verbose.
        /// </summary>
        public static bool IsVerbose = true;
        /// <summary>
        /// True if a runner is orchestrating the test runs.
        /// </summary>
        public static bool IsRunnerAttached = false;
    }
 
    public static class TestUtilities
    {
        /// <returns>
        /// Returns the path to CPC
        /// </returns>
        public static string GetCPCDirectoryPath()
        {
            return Environment.ExpandEnvironmentVariables(@"%SYSTEMDRIVE%\CPC");
        }
 
        /// <returns>
        /// The path to the ViBenchToJson executable.
        /// </returns>
        public static string GetViBenchToJsonExeFilePath()
        {
            return Path.Combine(GetCPCDirectoryPath(), "ViBenchToJson.exe");
        }
 
        //
        // Process spawning and error handling.
        //
 
        /// <summary>
        /// The result of a ShellOut completing.
        /// </summary>
        public class ProcessResult
        {
            /// <summary>
            /// The path to the executable that was run.
            /// </summary>
            public string ExecutablePath { get; set; }
            /// <summary>
            /// The arguments that were passed to the process.
            /// </summary>
            public string Args { get; set; }
            /// <summary>
            /// The exit code of the process.
            /// </summary>
            public int Code { get; set; }
            /// <summary>
            /// The entire standard-out of the process.
            /// </summary>
            public string StdOut { get; set; }
            /// <summary>
            /// The entire standard-error of the process.
            /// </summary>
            public string StdErr { get; set; }
 
            /// <summary>
            /// True if the command returned an exit code other
            /// than zero.
            /// </summary>
            public bool Failed => Code != 0;
            /// <summary>
            /// True if the command returned an exit code of 0.
            /// </summary>
            public bool Succeeded => !Failed;
        }
 
        /// <summary>
        /// Shells out, and if the process fails, log the error and quit the script.
        /// </summary>
        public static void ShellOutVital(
                string file,
                string args,
                string workingDirectory = null,
                CancellationToken cancellationToken = default(CancellationToken))
        {
            var result = ShellOut(file, args, workingDirectory, cancellationToken);
            if (result.Failed)
            {
                LogProcessResult(result);
                throw new System.Exception("ShellOutVital Failed");
            }
        }
 
        /// <summary>
        /// Shells out, blocks, and returns the ProcessResult.
        /// </summary>
        public static ProcessResult ShellOut(
                string file,
                string args,
                string workingDirectory = null,
                CancellationToken cancellationToken = default(CancellationToken))
        {
            workingDirectory ??= AppDomain.CurrentDomain.BaseDirectory;
 
            var tcs = new TaskCompletionSource<ProcessResult>();
            var startInfo = new ProcessStartInfo(file, args);
            startInfo.RedirectStandardOutput = true;
            startInfo.RedirectStandardError = true;
            startInfo.UseShellExecute = false;
            startInfo.WorkingDirectory = workingDirectory;
            var process = new Process
            {
                StartInfo = startInfo,
                EnableRaisingEvents = true,
            };
 
            if (cancellationToken != default(CancellationToken))
            {
                cancellationToken.Register(() => process.Kill());
            }
 
            if (RuntimeSettings.IsVerbose)
            {
                Log($"running \"{file}\" with arguments \"{args}\" from directory {workingDirectory}");
            }
 
            process.Start();
 
            var output = new StringWriter();
            var error = new StringWriter();
 
            process.OutputDataReceived += (s, e) =>
            {
                if (!String.IsNullOrEmpty(e.Data))
                {
                    output.WriteLine(e.Data);
                }
            };
 
            process.ErrorDataReceived += (s, e) =>
            {
                if (!String.IsNullOrEmpty(e.Data))
                {
                    error.WriteLine(e.Data);
                }
            };
 
            process.BeginOutputReadLine();
            process.BeginErrorReadLine();
            process.WaitForExit();
 
            return new ProcessResult
            {
                ExecutablePath = file,
                Args = args,
                Code = process.ExitCode,
                StdOut = output.ToString(),
                StdErr = error.ToString(),
            };
        }
 
        /// <summary>
        /// Shells out and returns the string gathered from the stdout of the 
        /// executing process.
        /// 
        /// Throws an exception if the process fails.
        /// </summary>
        public static string StdoutFrom(string program, string args = "", string workingDirectory = null)
        {
            var result = ShellOut(program, args, workingDirectory);
            if (result.Failed)
            {
                LogProcessResult(result);
                throw new Exception("Shelling out failed");
            }
            return result.StdOut.Trim();
        }
 
        /// <summary>
        /// Logs a message.
        ///
        /// The actual implementation of this method may change depending on
        /// if the script is being run standalone or through the test runner.
        /// </summary>
        public static void Log(string info)
        {
            RuntimeSettings.Logger.Log(info);
            RuntimeSettings.Logger.Flush();
        }
 
        /// <summary>
        /// Logs the result of a finished process
        /// </summary>
        public static void LogProcessResult(ProcessResult result)
        {
            RuntimeSettings.Logger.Log(String.Format("The process \"{0}\" {1} with code {2}",
                $"{result.ExecutablePath} {result.Args}",
                result.Failed ? "failed" : "succeeded",
                result.Code));
            RuntimeSettings.Logger.Log($"Standard Out:\n{result.StdOut}");
            RuntimeSettings.Logger.Log($"\nStandard Error:\n{result.StdErr}");
        }
 
        /// <summary>
        /// Either runs the provided tests, or schedules them to be run by the 
        /// runner. 
        /// </summary>
        public static void TestThisPlease(params PerfTest[] tests)
        {
            if (RuntimeSettings.IsRunnerAttached)
            {
                RuntimeSettings.ResultTests = tests;
            }
            else
            {
                foreach (var test in tests)
                {
                    test.Setup();
                    for (int i = 0; i < test.Iterations; i++)
                    {
                        test.Test();
                    }
                }
            }
        }
    }
}