|
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
using System.Globalization;
using System.Linq;
using Microsoft.VisualStudio.TestPlatform.Common;
using Microsoft.VisualStudio.TestPlatform.Common.Interfaces;
using Microsoft.VisualStudio.TestPlatform.Common.Utilities;
using 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;
/// <summary>
/// Allows the user to specify a order of loading custom adapters from.
/// </summary>
internal class TestAdapterLoadingStrategyArgumentProcessor : IArgumentProcessor
{
/// <summary>
/// The name of the command line argument that the TestAdapterLoadingStrategyArgumentProcessor handles.
/// </summary>
public const string CommandName = "/TestAdapterLoadingStrategy";
private Lazy<IArgumentProcessorCapabilities>? _metadata;
private Lazy<IArgumentExecutor>? _executor;
/// <summary>
/// Gets the metadata.
/// </summary>
public Lazy<IArgumentProcessorCapabilities> Metadata
=> _metadata ??= new Lazy<IArgumentProcessorCapabilities>(() =>
new TestAdapterLoadingStrategyArgumentProcessorCapabilities());
/// <summary>
/// Gets or sets the executor.
/// </summary>
public Lazy<IArgumentExecutor>? Executor
{
get => _executor ??= new Lazy<IArgumentExecutor>(() =>
new TestAdapterLoadingStrategyArgumentExecutor(CommandLineOptions.Instance, RunSettingsManager.Instance, ConsoleOutput.Instance, new FileHelper()));
set => _executor = value;
}
}
/// <summary>
/// The argument capabilities.
/// </summary>
internal class TestAdapterLoadingStrategyArgumentProcessorCapabilities : BaseArgumentProcessorCapabilities
{
public override string CommandName => TestAdapterLoadingStrategyArgumentProcessor.CommandName;
public override bool AllowMultiple => false;
public override bool IsAction => false;
public override bool AlwaysExecute => true;
public override ArgumentProcessorPriority Priority => ArgumentProcessorPriority.TestAdapterLoadingStrategy;
public override string HelpContentResourceName => CommandLineResources.TestAdapterLoadingStrategyHelp;
public override HelpContentPriority HelpPriority => HelpContentPriority.TestAdapterLoadingStrategyArgumentProcessorHelpPriority;
}
/// <summary>
/// The argument executor.
/// </summary>
internal class TestAdapterLoadingStrategyArgumentExecutor : IArgumentExecutor
{
/// <summary>
/// Used for getting sources.
/// </summary>
private readonly CommandLineOptions _commandLineOptions;
/// <summary>
/// Run settings provider.
/// </summary>
private readonly IRunSettingsProvider _runSettingsManager;
/// <summary>
/// Used for sending output.
/// </summary>
private readonly IOutput _output;
/// <summary>
/// For file related operation
/// </summary>
private readonly IFileHelper _fileHelper;
public const string RunSettingsPath = "RunConfiguration.TestAdapterLoadingStrategy";
/// <summary>
/// Default constructor.
/// </summary>
/// <param name="options"> The options. </param>
/// <param name="runSettingsManager">Run setting manager.</param>
/// <param name="output">Output such as console.</param>
/// <param name="fileHelper">File helper</param>
public TestAdapterLoadingStrategyArgumentExecutor(CommandLineOptions options, IRunSettingsProvider runSettingsManager, IOutput output, IFileHelper fileHelper)
{
_commandLineOptions = options ?? throw new ArgumentNullException(nameof(options));
_runSettingsManager = runSettingsManager ?? throw new ArgumentNullException(nameof(runSettingsManager));
_output = output ?? throw new ArgumentNullException(nameof(output));
_fileHelper = fileHelper ?? throw new ArgumentNullException(nameof(fileHelper));
}
#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)
{
ExtractStrategy(argument, out var strategy);
if (strategy == TestAdapterLoadingStrategy.Recursive)
{
throw new CommandLineException(string.Format(CultureInfo.CurrentCulture, CommandLineResources.TestAdapterLoadingStrategyValueInvalidRecursive, $"{nameof(TestAdapterLoadingStrategy.Explicit)}, {nameof(TestAdapterLoadingStrategy.NextToSource)}"));
}
if (strategy == TestAdapterLoadingStrategy.Default)
{
InitializeDefaultStrategy();
return;
}
InitializeStrategy(strategy);
}
/// <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;
}
#endregion
private void ExtractStrategy(string? value, out TestAdapterLoadingStrategy strategy)
{
value ??= _runSettingsManager.QueryRunSettingsNode(RunSettingsPath);
if (value.IsNullOrWhiteSpace())
{
strategy = TestAdapterLoadingStrategy.Default;
return;
}
if (!Enum.TryParse(value, out strategy))
{
throw new CommandLineException(string.Format(CultureInfo.CurrentCulture, CommandLineResources.TestAdapterLoadingStrategyValueInvalid, value));
}
}
private void InitializeDefaultStrategy()
{
ValidateTestAdapterPaths(TestAdapterLoadingStrategy.Default);
SetStrategy(TestAdapterLoadingStrategy.Default);
}
private void InitializeStrategy(TestAdapterLoadingStrategy strategy)
{
ValidateTestAdapterPaths(strategy);
if (!_commandLineOptions.TestAdapterPathsSet && strategy.HasFlag(TestAdapterLoadingStrategy.Explicit))
{
throw new CommandLineException(string.Format(CultureInfo.CurrentCulture, CommandLineResources.TestAdapterPathValueRequiredWhenStrategyXIsUsed, nameof(TestAdapterLoadingStrategy.Explicit)));
}
SetStrategy(strategy);
}
private void ForceIsolation()
{
if (_commandLineOptions.InIsolation)
{
return;
}
EqtTrace.Warning(
$"{nameof(TestAdapterLoadingStrategyArgumentExecutor)}.{nameof(ForceIsolation)}: InIsolation setting is forced when {nameof(TestAdapterLoadingStrategy.Explicit)} strategy is used." +
"Tests will run in isolation."
);
_commandLineOptions.InIsolation = true;
_runSettingsManager.UpdateRunSettingsNode(InIsolationArgumentExecutor.RunSettingsPath, "true");
}
private void ValidateTestAdapterPaths(TestAdapterLoadingStrategy strategy)
{
var testAdapterPaths = _commandLineOptions.TestAdapterPath ?? [];
if (!_commandLineOptions.TestAdapterPathsSet)
{
testAdapterPaths = TestAdapterPathArgumentExecutor.SplitPaths(_runSettingsManager.QueryRunSettingsNode(TestAdapterPathArgumentExecutor.RunSettingsPath)).Union(testAdapterPaths).Distinct().ToArray();
}
for (var i = 0; i < testAdapterPaths.Length; i++)
{
var adapterPath = testAdapterPaths[i];
var testAdapterPath = _fileHelper.GetFullPath(Environment.ExpandEnvironmentVariables(adapterPath));
if (strategy == TestAdapterLoadingStrategy.Default && !_fileHelper.DirectoryExists(testAdapterPath))
{
throw new CommandLineException(
string.Format(CultureInfo.CurrentCulture, CommandLineResources.InvalidTestAdapterPathCommand, adapterPath, CommandLineResources.TestAdapterPathDoesNotExist)
);
}
testAdapterPaths[i] = testAdapterPath;
}
_runSettingsManager.UpdateRunSettingsNode(TestAdapterPathArgumentExecutor.RunSettingsPath, string.Join(";", testAdapterPaths));
}
private void SetStrategy(TestAdapterLoadingStrategy strategy)
{
_commandLineOptions.TestAdapterLoadingStrategy = strategy;
_runSettingsManager.UpdateRunSettingsNode(RunSettingsPath, strategy.ToString());
if (strategy.HasFlag(TestAdapterLoadingStrategy.Explicit))
{
ForceIsolation();
}
}
}
|