| File: Processors\EnableDiagArgumentProcessor.cs | Web Access |
| Project: src\vstest\src\vstest.console\vstest.console.csproj (vstest.console) |
// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. extern alias Abstraction; using System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; using Microsoft.VisualStudio.TestPlatform.CommandLine.Internal; using Microsoft.VisualStudio.TestPlatform.CommandLine.Processors.Utilities; using Microsoft.VisualStudio.TestPlatform.ObjectModel; using Abstraction::Microsoft.VisualStudio.TestPlatform.PlatformAbstractions; using Abstraction::Microsoft.VisualStudio.TestPlatform.PlatformAbstractions.Interfaces; using Abstraction::Microsoft.VisualStudio.TestPlatform.ObjectModel; using Microsoft.VisualStudio.TestPlatform.Utilities; using Microsoft.VisualStudio.TestPlatform.Utilities.Helpers; using Microsoft.VisualStudio.TestPlatform.Utilities.Helpers.Interfaces; using CommandLineResources = Microsoft.VisualStudio.TestPlatform.CommandLine.Resources.Resources; namespace Microsoft.VisualStudio.TestPlatform.CommandLine.Processors; internal class EnableDiagArgumentProcessor : IArgumentProcessor { /// <summary> /// The name of the command line argument that the ListTestsArgumentExecutor handles. /// </summary> public const string CommandName = "/Diag"; private readonly IFileHelper _fileHelper; private Lazy<IArgumentProcessorCapabilities>? _metadata; private Lazy<IArgumentExecutor>? _executor; /// <summary> /// Initializes a new instance of the <see cref="EnableDiagArgumentProcessor"/> class. /// </summary> public EnableDiagArgumentProcessor() : this(new FileHelper()) { } /// <summary> /// Initializes a new instance of the <see cref="EnableDiagArgumentProcessor"/> class. /// </summary> /// <param name="fileHelper">A file helper instance.</param> protected EnableDiagArgumentProcessor(IFileHelper fileHelper) { _fileHelper = fileHelper; } public Lazy<IArgumentProcessorCapabilities> Metadata => _metadata ??= new Lazy<IArgumentProcessorCapabilities>(() => new EnableDiagArgumentProcessorCapabilities()); /// <summary> /// Gets or sets the executor. /// </summary> public Lazy<IArgumentExecutor>? Executor { get => _executor ??= new Lazy<IArgumentExecutor>(() => new EnableDiagArgumentExecutor(_fileHelper, new ProcessHelper())); set => _executor = value; } } /// <summary> /// The argument capabilities. /// </summary> internal class EnableDiagArgumentProcessorCapabilities : BaseArgumentProcessorCapabilities { public override string CommandName => EnableDiagArgumentProcessor.CommandName; public override bool AllowMultiple => false; public override bool IsAction => false; public override ArgumentProcessorPriority Priority => ArgumentProcessorPriority.Diag; public override string HelpContentResourceName => CommandLineResources.EnableDiagUsage; public override HelpContentPriority HelpPriority => HelpContentPriority.EnableDiagArgumentProcessorHelpPriority; } /// <summary> /// The argument executor. /// </summary> internal class EnableDiagArgumentExecutor : IArgumentExecutor { private readonly IFileHelper _fileHelper; private readonly IProcessHelper _processHelper; /// <summary> /// Parameter for trace level /// </summary> public const string TraceLevelParam = "tracelevel"; /// <summary> /// Default constructor. /// </summary> /// <param name="fileHelper">The file helper.</param> /// <param name="processHelper">Process helper</param> public EnableDiagArgumentExecutor(IFileHelper fileHelper, IProcessHelper processHelper) { _fileHelper = fileHelper; _processHelper = processHelper; } #region IArgumentExecutor /// <summary> /// Initializes with the argument that was provided with the command. /// </summary> /// <param name="argument">Argument that was provided with the command.</param> public void Initialize(string? argument) { string exceptionMessage = string.Format(CultureInfo.CurrentCulture, CommandLineResources.InvalidDiagArgument, argument); // Throw error if argument is null or empty. if (argument.IsNullOrWhiteSpace()) { throw new CommandLineException(exceptionMessage); } // Get diag argument list. var diagArgumentList = ArgumentProcessorUtilities.GetArgumentList(argument, ArgumentProcessorUtilities.SemiColonArgumentSeparator, exceptionMessage); // Get diag file path. // Note: Even though semi colon is valid file path, we are not respecting the file name having semi-colon [As we are separating arguments based on semi colon]. var diagFilePathArg = diagArgumentList[0]; var diagFilePath = GetDiagFilePath(diagFilePathArg); // Get diag parameters. var diagParameterArgs = diagArgumentList.Skip(1); var diagParameters = ArgumentProcessorUtilities.GetArgumentParameters(diagParameterArgs, ArgumentProcessorUtilities.EqualNameValueSeparator, exceptionMessage); // Initialize diag logging. InitializeDiagLogging(diagFilePath, diagParameters); // Write version to the log here, because that is the // first place where we know if we log or not. EqtTrace.Verbose($"Version: {Product.Version} Current process architecture: {_processHelper.GetCurrentProcessArchitecture()}"); // https://docs.microsoft.com/en-us/dotnet/api/system.reflection.assembly.location?view=net-6.0#remarks // In .NET 5 and later versions, for bundled assemblies, the value returned is an empty string. string objectTypeLocation = typeof(object).Assembly.Location; if (!objectTypeLocation.IsNullOrEmpty()) { EqtTrace.Verbose($"Runtime location: {Path.GetDirectoryName(objectTypeLocation)}"); } } /// <summary> /// Executes the argument processor. /// </summary> /// <returns>The <see cref="ArgumentProcessorResult"/>.</returns> public ArgumentProcessorResult Execute() { // Nothing to do since we updated the parameter during initialize parameter return ArgumentProcessorResult.Success; } /// <summary> /// Initialize diag logging. /// </summary> /// <param name="diagFilePath">Diag file path.</param> /// <param name="diagParameters">Diag parameters</param> private static void InitializeDiagLogging(string diagFilePath, Dictionary<string, string> diagParameters) { // Get trace level from diag parameters. var traceLevel = GetDiagTraceLevel(diagParameters); // Initialize trace. // Trace initialized is false in case of any exception at time of initialization like Catch exception(UnauthorizedAccessException, PathTooLongException...) var traceInitialized = EqtTrace.InitializeTrace(diagFilePath, traceLevel); // Show console warning in case trace is not initialized. if (!traceInitialized && !StringUtils.IsNullOrEmpty(EqtTrace.ErrorOnInitialization)) { ConsoleOutput.Instance.Warning(false, EqtTrace.ErrorOnInitialization); } } /// <summary> /// Gets diag trace level. /// </summary> /// <param name="diagParameters">Diag parameters.</param> /// <returns>Diag trace level.</returns> private static PlatformTraceLevel GetDiagTraceLevel(Dictionary<string, string> diagParameters) { // If diag parameters is null, set value of trace level as verbose. if (diagParameters == null) { return PlatformTraceLevel.Verbose; } // Get trace level from diag parameters. var traceLevelExists = diagParameters.TryGetValue(TraceLevelParam, out var traceLevelStr); if (traceLevelExists && Enum.TryParse(traceLevelStr, true, out PlatformTraceLevel traceLevel)) { return traceLevel; } // Default value of diag trace level is verbose. return PlatformTraceLevel.Verbose; } /// <summary> /// Gets diag file path. /// </summary> /// <param name="diagFilePathArgument">Diag file path argument.</param> /// <returns>Diag file path.</returns> private string GetDiagFilePath(string diagFilePathArgument) { // Remove double quotes if present. diagFilePathArgument = diagFilePathArgument.Replace("\"", ""); // If we provide a directory we don't need to create the base directory. if (!diagFilePathArgument.EndsWith(@"\") && !diagFilePathArgument.EndsWith("/")) { // Create base directory for diag file path (if doesn't exist) CreateDirectoryIfNotExists(diagFilePathArgument); } // return full diag file path. (This is done so that vstest and testhost create logs at same location.) return Path.GetFullPath(diagFilePathArgument); } /// <summary> /// Create directory if not exists. /// </summary> /// <param name="filePath">File path.</param> private void CreateDirectoryIfNotExists(string filePath) { // Create the base directory of file path if doesn't exist. // Directory could be empty if just a filename is provided. E.g. log.txt var directory = Path.GetDirectoryName(filePath); if (!StringUtils.IsNullOrEmpty(directory) && !_fileHelper.DirectoryExists(directory)) { _fileHelper.CreateDirectory(directory); } } #endregion }