diff --git a/TestPlatform.sln b/TestPlatform.sln
index b4b03614bd..5824a83e66 100644
--- a/TestPlatform.sln
+++ b/TestPlatform.sln
@@ -170,7 +170,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AttachVS", "src\AttachVS\At
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "playground", "playground", "{6CE2F530-582B-4695-A209-41065E103426}"
ProjectSection(SolutionItems) = preProject
- README.md = README.md
+ playground\README.md = playground\README.md
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestPlatform.Playground", "playground\TestPlatform.Playground\TestPlatform.Playground.csproj", "{545A88D3-1AE2-4D39-9B7C-C691768AD17F}"
diff --git a/src/Microsoft.TestPlatform.Client/TestPlatform.cs b/src/Microsoft.TestPlatform.Client/TestPlatform.cs
index 9d3115d4a8..2676096fdd 100644
--- a/src/Microsoft.TestPlatform.Client/TestPlatform.cs
+++ b/src/Microsoft.TestPlatform.Client/TestPlatform.cs
@@ -12,24 +12,24 @@ namespace Microsoft.VisualStudio.TestPlatform.Client;
using System.Linq;
using System.Reflection;
-using Discovery;
-using Execution;
-using Common;
-using Common.ExtensionFramework;
-using Common.Hosting;
-using Common.Logging;
+using Microsoft.VisualStudio.TestPlatform.Client.Discovery;
+using Microsoft.VisualStudio.TestPlatform.Client.Execution;
+using Microsoft.VisualStudio.TestPlatform.Common;
+using Microsoft.VisualStudio.TestPlatform.Common.ExtensionFramework;
+using Microsoft.VisualStudio.TestPlatform.Common.Hosting;
+using Microsoft.VisualStudio.TestPlatform.Common.Logging;
using Microsoft.VisualStudio.TestPlatform.Common.Utilities;
-using CrossPlatEngine;
-using ObjectModel;
+using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine;
+using Microsoft.VisualStudio.TestPlatform.ObjectModel;
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client;
-using ObjectModel.Engine;
-using ObjectModel.Host;
+using Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine;
+using Microsoft.VisualStudio.TestPlatform.ObjectModel.Host;
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Utilities;
-using PlatformAbstractions;
-using Utilities.Helpers;
-using Utilities.Helpers.Interfaces;
+using Microsoft.VisualStudio.TestPlatform.PlatformAbstractions;
+using Microsoft.VisualStudio.TestPlatform.Utilities.Helpers;
+using Microsoft.VisualStudio.TestPlatform.Utilities.Helpers.Interfaces;
-using ClientResources = Resources.Resources;
+using ClientResources = Microsoft.VisualStudio.TestPlatform.Client.Resources.Resources;
///
/// Implementation for TestPlatform.
@@ -87,27 +87,18 @@ public IDiscoveryRequest CreateDiscoveryRequest(
DiscoveryCriteria discoveryCriteria!!,
TestPlatformOptions options)
{
-
- // Update cache with Extension folder's files.
- AddExtensionAssemblies(discoveryCriteria.RunSettings);
-
- // Update extension assemblies from source when design mode is false.
- var runConfiguration = XmlRunSettingsUtilities.GetRunConfigurationNode(discoveryCriteria.RunSettings);
- if (!runConfiguration.DesignMode)
- {
- AddExtensionAssembliesFromSource(discoveryCriteria.Sources);
- }
+ PopulateExtensions(discoveryCriteria.RunSettings, discoveryCriteria.Sources);
// Initialize loggers.
- var loggerManager = TestEngine.GetLoggerManager(requestData);
+ ITestLoggerManager loggerManager = TestEngine.GetLoggerManager(requestData);
loggerManager.Initialize(discoveryCriteria.RunSettings);
- var testHostManager = _testHostProviderManager.GetTestHostManagerByRunConfiguration(discoveryCriteria.RunSettings);
- ThrowExceptionIfTestHostManagerIsNull(testHostManager, discoveryCriteria.RunSettings);
+ ITestRuntimeProvider testHostManager = _testHostProviderManager.GetTestHostManagerByRunConfiguration(discoveryCriteria.RunSettings);
+ TestPlatform.ThrowExceptionIfTestHostManagerIsNull(testHostManager, discoveryCriteria.RunSettings);
testHostManager.Initialize(TestSessionMessageLogger.Instance, discoveryCriteria.RunSettings);
- var discoveryManager = TestEngine.GetDiscoveryManager(requestData, testHostManager, discoveryCriteria);
+ IProxyDiscoveryManager discoveryManager = TestEngine.GetDiscoveryManager(requestData, testHostManager, discoveryCriteria);
discoveryManager.Initialize(options?.SkipDefaultAdapters ?? false);
return new DiscoveryRequest(requestData, discoveryCriteria, discoveryManager, loggerManager);
@@ -119,22 +110,15 @@ public ITestRunRequest CreateTestRunRequest(
TestRunCriteria testRunCriteria!!,
TestPlatformOptions options)
{
- AddExtensionAssemblies(testRunCriteria.TestRunSettings);
-
- var runConfiguration = XmlRunSettingsUtilities.GetRunConfigurationNode(testRunCriteria.TestRunSettings);
-
- // Update extension assemblies from source when design mode is false.
- if (!runConfiguration.DesignMode)
- {
- AddExtensionAssembliesFromSource(testRunCriteria);
- }
+ IEnumerable sources = GetSources(testRunCriteria);
+ PopulateExtensions(testRunCriteria.TestRunSettings, sources);
// Initialize loggers.
- var loggerManager = TestEngine.GetLoggerManager(requestData);
+ ITestLoggerManager loggerManager = TestEngine.GetLoggerManager(requestData);
loggerManager.Initialize(testRunCriteria.TestRunSettings);
- var testHostManager = _testHostProviderManager.GetTestHostManagerByRunConfiguration(testRunCriteria.TestRunSettings);
- ThrowExceptionIfTestHostManagerIsNull(testHostManager, testRunCriteria.TestRunSettings);
+ ITestRuntimeProvider testHostManager = _testHostProviderManager.GetTestHostManagerByRunConfiguration(testRunCriteria.TestRunSettings);
+ TestPlatform.ThrowExceptionIfTestHostManagerIsNull(testHostManager, testRunCriteria.TestRunSettings);
testHostManager.Initialize(TestSessionMessageLogger.Instance, testRunCriteria.TestRunSettings);
@@ -144,7 +128,7 @@ public ITestRunRequest CreateTestRunRequest(
testHostManager.SetCustomLauncher(testRunCriteria.TestHostLauncher);
}
- var executionManager = TestEngine.GetExecutionManager(requestData, testHostManager, testRunCriteria);
+ IProxyExecutionManager executionManager = TestEngine.GetExecutionManager(requestData, testHostManager, testRunCriteria);
executionManager.Initialize(options?.SkipDefaultAdapters ?? false);
return new TestRunRequest(requestData, testRunCriteria, executionManager, loggerManager);
@@ -156,15 +140,17 @@ public bool StartTestSession(
StartTestSessionCriteria testSessionCriteria!!,
ITestSessionEventsHandler eventsHandler)
{
- AddExtensionAssemblies(testSessionCriteria.RunSettings);
+ RunConfiguration runConfiguration = XmlRunSettingsUtilities.GetRunConfigurationNode(testSessionCriteria.RunSettings);
+ TestAdapterLoadingStrategy strategy = runConfiguration.TestAdapterLoadingStrategy;
+
+ AddExtensionAssemblies(testSessionCriteria.RunSettings, strategy);
- var runConfiguration = XmlRunSettingsUtilities.GetRunConfigurationNode(testSessionCriteria.RunSettings);
if (!runConfiguration.DesignMode)
{
return false;
}
- var testSessionManager = TestEngine.GetTestSessionManager(requestData, testSessionCriteria);
+ IProxyTestSessionManager testSessionManager = TestEngine.GetTestSessionManager(requestData, testSessionCriteria);
if (testSessionManager == null)
{
// The test session manager is null because the combination of runsettings and
@@ -178,6 +164,21 @@ public bool StartTestSession(
return testSessionManager.StartSession(eventsHandler, requestData);
}
+ private void PopulateExtensions(string runSettings, IEnumerable sources)
+ {
+ RunConfiguration runConfiguration = XmlRunSettingsUtilities.GetRunConfigurationNode(runSettings);
+ TestAdapterLoadingStrategy strategy = runConfiguration.TestAdapterLoadingStrategy;
+
+ // Update cache with Extension folder's files.
+ AddExtensionAssemblies(runSettings, strategy);
+
+ // Update extension assemblies from source when design mode is false.
+ if (!runConfiguration.DesignMode)
+ {
+ AddLoggerAssembliesFromSource(sources, strategy);
+ }
+ }
+
///
/// The dispose.
///
@@ -200,24 +201,19 @@ public void ClearExtensions()
TestEngine.GetExtensionManager().ClearExtensions();
}
- private void ThrowExceptionIfTestHostManagerIsNull(
+ private static void ThrowExceptionIfTestHostManagerIsNull(
ITestRuntimeProvider testHostManager,
string settingsXml)
{
if (testHostManager == null)
{
- EqtTrace.Error("TestPlatform.CreateTestRunRequest: No suitable testHostProvider found for runsettings : {0}", settingsXml);
+ EqtTrace.Error($"{nameof(TestPlatform)}.{nameof(ThrowExceptionIfTestHostManagerIsNull)}: No suitable testHostProvider found for runsettings: {settingsXml}");
throw new TestPlatformException(string.Format(CultureInfo.CurrentCulture, ClientResources.NoTestHostProviderFound));
}
}
- ///
- /// Updates the test adapter paths provided through run settings to be used by the test
- /// service.
- ///
- ///
- /// The run settings.
- private void AddExtensionAssemblies(string runSettings)
+
+ private void AddExtensionAssemblies(string runSettings, TestAdapterLoadingStrategy adapterLoadingStrategy)
{
IEnumerable customTestAdaptersPaths = RunSettingsUtilities.GetTestAdaptersPaths(runSettings);
@@ -225,67 +221,44 @@ private void AddExtensionAssemblies(string runSettings)
{
foreach (string customTestAdaptersPath in customTestAdaptersPaths)
{
- var adapterPath = Path.GetFullPath(Environment.ExpandEnvironmentVariables(customTestAdaptersPath));
- if (!Directory.Exists(adapterPath))
- {
- EqtTrace.Warning($"AdapterPath Not Found: {adapterPath}");
- continue;
- }
+ IEnumerable extensionAssemblies = ExpandTestAdapterPaths(customTestAdaptersPath, _fileHelper, adapterLoadingStrategy);
- var extensionAssemblies = new List(
- _fileHelper.EnumerateFiles(
- adapterPath,
- SearchOption.AllDirectories,
- TestPlatformConstants.TestAdapterEndsWithPattern,
- TestPlatformConstants.TestLoggerEndsWithPattern,
- TestPlatformConstants.DataCollectorEndsWithPattern,
- TestPlatformConstants.RunTimeEndsWithPattern));
-
- if (extensionAssemblies.Count > 0)
+ if (extensionAssemblies.Any())
{
UpdateExtensions(extensionAssemblies, skipExtensionFilters: false);
}
+
}
}
}
///
- /// Updates the extension assemblies from source directory.
+ /// Updates the test logger paths from source directory.
///
///
- /// The test run criteria.
- private void AddExtensionAssembliesFromSource(TestRunCriteria testRunCriteria)
+ /// The list of sources.
+ private void AddLoggerAssembliesFromSource(IEnumerable sources, TestAdapterLoadingStrategy strategy)
{
- IEnumerable sources = testRunCriteria.Sources;
- if (testRunCriteria.HasSpecificTests)
+ // Skip discovery unless we're using the default behavior, or NextToSource is specified.
+ if (strategy != TestAdapterLoadingStrategy.Default && strategy.HasFlag(TestAdapterLoadingStrategy.NextToSource))
{
- // If the test execution is with a test filter, group them by sources.
- sources = testRunCriteria.Tests.Select(tc => tc.Source).Distinct();
+ return;
}
- AddExtensionAssembliesFromSource(sources);
- }
-
- ///
- /// Updates the test logger paths from source directory.
- ///
- ///
- /// The list of sources.
- private void AddExtensionAssembliesFromSource(IEnumerable sources)
- {
// Currently we support discovering loggers only from Source directory.
- var loggersToUpdate = new List();
+ List loggersToUpdate = new();
- foreach (var source in sources)
+ foreach (string source in sources)
{
- var sourceDirectory = Path.GetDirectoryName(source);
- if (!string.IsNullOrEmpty(sourceDirectory)
- && _fileHelper.DirectoryExists(sourceDirectory))
+ string sourceDirectory = Path.GetDirectoryName(source);
+ if (!string.IsNullOrEmpty(sourceDirectory) && _fileHelper.DirectoryExists(sourceDirectory))
{
+ SearchOption searchOption = GetSearchOption(strategy, SearchOption.TopDirectoryOnly);
+
loggersToUpdate.AddRange(
_fileHelper.EnumerateFiles(
sourceDirectory,
- SearchOption.TopDirectoryOnly,
+ searchOption,
TestPlatformConstants.TestLoggerEndsWithPattern));
}
}
@@ -303,21 +276,127 @@ private void AddExtensionAssembliesFromSource(IEnumerable sources)
///
private static void AddExtensionAssembliesFromExtensionDirectory()
{
- var fileHelper = new FileHelper();
- var extensionsFolder = Path.Combine(
- Path.GetDirectoryName(
- typeof(TestPlatform).GetTypeInfo().Assembly.GetAssemblyLocation()),
- "Extensions");
+ // This method needs to run statically before we have any adapter discovery.
+ // TestHostProviderManager get initialized just after this call and it
+ // requires DefaultExtensionPaths to be set to resolve a TestHostProvider.
+ // Since it's static, it forces us to set the adapter paths.
+ //
+ // Otherwise we will always get a "No suitable test runtime provider found for this run." error.
+ // I (@haplois) will modify this behavior later on, but we also need to consider legacy adapters
+ // and make sure they still work after modification.
+ string runSettings = RunSettingsManager.Instance.ActiveRunSettings.SettingsXml;
+ RunConfiguration runConfiguration = XmlRunSettingsUtilities.GetRunConfigurationNode(runSettings);
+ TestAdapterLoadingStrategy strategy = runConfiguration.TestAdapterLoadingStrategy;
+
+ FileHelper fileHelper = new();
+ IEnumerable defaultExtensionPaths = Enumerable.Empty();
+
+ // Explicit adapter loading
+ if (strategy.HasFlag(TestAdapterLoadingStrategy.Explicit))
+ {
+ defaultExtensionPaths = RunSettingsUtilities.GetTestAdaptersPaths(runSettings)
+ .SelectMany(path => ExpandTestAdapterPaths(path, fileHelper, strategy))
+ .Union(defaultExtensionPaths);
+ }
+ string extensionsFolder = Path.Combine(Path.GetDirectoryName(typeof(TestPlatform).GetTypeInfo().Assembly.GetAssemblyLocation()), "Extensions");
if (fileHelper.DirectoryExists(extensionsFolder))
{
- var defaultExtensionPaths = fileHelper.EnumerateFiles(
- extensionsFolder,
- SearchOption.TopDirectoryOnly,
- ".dll",
- ".exe");
+ // Load default runtime providers
+ if (strategy.HasFlag(TestAdapterLoadingStrategy.DefaultRuntimeProviders))
+ {
+ defaultExtensionPaths = fileHelper
+ .EnumerateFiles(extensionsFolder, SearchOption.TopDirectoryOnly, TestPlatformConstants.RunTimeEndsWithPattern)
+ .Union(defaultExtensionPaths);
+ }
- TestPluginCache.Instance.DefaultExtensionPaths = defaultExtensionPaths;
+ // Default extension loader
+ if (strategy == TestAdapterLoadingStrategy.Default || strategy.HasFlag(TestAdapterLoadingStrategy.ExtensionsDirectory))
+ {
+ defaultExtensionPaths = fileHelper
+ .EnumerateFiles(extensionsFolder, SearchOption.TopDirectoryOnly, ".dll", ".exe")
+ .Union(defaultExtensionPaths);
+ }
}
+
+ TestPluginCache.Instance.DefaultExtensionPaths = defaultExtensionPaths.Distinct();
}
+
+ private static SearchOption GetSearchOption(TestAdapterLoadingStrategy strategy, SearchOption defaultStrategyOption)
+ {
+ return strategy == TestAdapterLoadingStrategy.Default
+ ? defaultStrategyOption
+ : strategy.HasFlag(TestAdapterLoadingStrategy.Recursive) ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;
+ }
+
+ private static IEnumerable ExpandTestAdapterPaths(string path, IFileHelper fileHelper, TestAdapterLoadingStrategy strategy)
+ {
+ string adapterPath = Path.GetFullPath(Environment.ExpandEnvironmentVariables(path));
+
+ // Default behavior is to only accept directories.
+ if (strategy == TestAdapterLoadingStrategy.Default)
+ {
+ return ExpandAdaptersWithDefaultStrategy(adapterPath, fileHelper);
+ }
+
+ IEnumerable adapters = ExpandAdaptersWithExplicitStrategy(adapterPath, fileHelper, strategy);
+
+ return adapters.Distinct();
+ }
+
+ private static IEnumerable ExpandAdaptersWithExplicitStrategy(string path, IFileHelper fileHelper, TestAdapterLoadingStrategy strategy)
+ {
+ if (strategy.HasFlag(TestAdapterLoadingStrategy.Explicit))
+ {
+ return Enumerable.Empty();
+ }
+
+ if (fileHelper.Exists(path))
+ {
+ return new[] { path };
+ }
+ else if (fileHelper.DirectoryExists(path))
+ {
+ SearchOption searchOption = GetSearchOption(strategy, SearchOption.TopDirectoryOnly);
+
+ IEnumerable adapterPaths = fileHelper.EnumerateFiles(
+ path,
+ searchOption,
+ TestPlatformConstants.TestAdapterEndsWithPattern,
+ TestPlatformConstants.TestLoggerEndsWithPattern,
+ TestPlatformConstants.DataCollectorEndsWithPattern,
+ TestPlatformConstants.RunTimeEndsWithPattern);
+
+ return adapterPaths;
+ }
+
+ EqtTrace.Warning($"{nameof(TestPlatform)}.{nameof(ExpandAdaptersWithExplicitStrategy)} AdapterPath Not Found: {path}");
+ return Enumerable.Empty();
+ }
+
+ private static IEnumerable ExpandAdaptersWithDefaultStrategy(string path, IFileHelper fileHelper)
+ {
+ // This is the legacy behavior, please do not modify this method unless you're sure of
+ // side effect when running tests with legacy adapters.
+ if (!fileHelper.DirectoryExists(path))
+ {
+ EqtTrace.Warning($"{nameof(TestPlatform)}.{nameof(ExpandAdaptersWithDefaultStrategy)} AdapterPath Not Found: {path}");
+
+ return Enumerable.Empty();
+ }
+
+ return fileHelper.EnumerateFiles(
+ path,
+ SearchOption.AllDirectories,
+ TestPlatformConstants.TestAdapterEndsWithPattern,
+ TestPlatformConstants.TestLoggerEndsWithPattern,
+ TestPlatformConstants.DataCollectorEndsWithPattern,
+ TestPlatformConstants.RunTimeEndsWithPattern);
+ }
+
+ private static IEnumerable GetSources(TestRunCriteria testRunCriteria) =>
+ testRunCriteria.HasSpecificTests
+ // If the test execution is with a test filter, filter sources too.
+ ? testRunCriteria.Tests.Select(tc => tc.Source).Distinct()
+ : testRunCriteria.Sources;
}
diff --git a/src/Microsoft.TestPlatform.Common/ExtensionFramework/DataCollectionExtensionManager.cs b/src/Microsoft.TestPlatform.Common/ExtensionFramework/DataCollectionExtensionManager.cs
index f9f1b727b1..8900da5b8e 100644
--- a/src/Microsoft.TestPlatform.Common/ExtensionFramework/DataCollectionExtensionManager.cs
+++ b/src/Microsoft.TestPlatform.Common/ExtensionFramework/DataCollectionExtensionManager.cs
@@ -53,7 +53,7 @@ protected DataCollectorExtensionManager(
///
public static DataCollectorExtensionManager Create(IMessageLogger messageLogger)
{
- TestPluginManager.Instance.GetSpecificTestExtensions(
+ TestPluginManager.GetSpecificTestExtensions(
TestPlatformConstants.DataCollectorEndsWithPattern,
out var unfilteredTestExtensions,
out var filteredTestExtensions);
@@ -78,7 +78,7 @@ public static DataCollectorExtensionManager Create(IMessageLogger messageLogger)
///
public static DataCollectorExtensionManager Create(string extensionAssemblyFilePath, bool skipCache, IMessageLogger messageLogger)
{
- TestPluginManager.Instance.GetTestExtensions(
+ TestPluginManager.GetTestExtensions(
extensionAssemblyFilePath,
out var unfilteredTestExtensions,
out var filteredTestExtensions,
diff --git a/src/Microsoft.TestPlatform.Common/ExtensionFramework/TestDiscoveryExtensionManager.cs b/src/Microsoft.TestPlatform.Common/ExtensionFramework/TestDiscoveryExtensionManager.cs
index b15c48f77c..7c8dd21d9f 100644
--- a/src/Microsoft.TestPlatform.Common/ExtensionFramework/TestDiscoveryExtensionManager.cs
+++ b/src/Microsoft.TestPlatform.Common/ExtensionFramework/TestDiscoveryExtensionManager.cs
@@ -64,8 +64,7 @@ public static TestDiscoveryExtensionManager Create()
if (s_testDiscoveryExtensionManager == null)
{
- TestPluginManager.Instance
- .GetSpecificTestExtensions(
+ TestPluginManager.GetSpecificTestExtensions(
TestPlatformConstants.TestAdapterEndsWithPattern,
out var unfilteredTestExtensions,
out var testExtensions);
@@ -90,8 +89,7 @@ public static TestDiscoveryExtensionManager Create()
public static TestDiscoveryExtensionManager GetDiscoveryExtensionManager(string extensionAssembly)
{
- TestPluginManager.Instance
- .GetTestExtensions(
+ TestPluginManager.GetTestExtensions(
extensionAssembly,
out var unfilteredTestExtensions,
out var testExtensions);
diff --git a/src/Microsoft.TestPlatform.Common/ExtensionFramework/TestExecutorExtensionManager.cs b/src/Microsoft.TestPlatform.Common/ExtensionFramework/TestExecutorExtensionManager.cs
index 524fc3ff71..6e431591c4 100644
--- a/src/Microsoft.TestPlatform.Common/ExtensionFramework/TestExecutorExtensionManager.cs
+++ b/src/Microsoft.TestPlatform.Common/ExtensionFramework/TestExecutorExtensionManager.cs
@@ -107,15 +107,13 @@ internal static TestExecutorExtensionManager Create()
{
// Get all extensions for ITestExecutor.
- TestPluginManager.Instance
- .GetSpecificTestExtensions(
+ TestPluginManager.GetSpecificTestExtensions(
TestPlatformConstants.TestAdapterEndsWithPattern,
out var unfilteredTestExtensions1,
out var testExtensions1);
// Get all extensions for ITestExecutor2.
- TestPluginManager.Instance
- .GetSpecificTestExtensions(
+ TestPluginManager.GetSpecificTestExtensions(
TestPlatformConstants.TestAdapterEndsWithPattern,
out var unfilteredTestExtensions2,
out var testExtensions2);
@@ -152,15 +150,13 @@ internal static TestExecutorExtensionManager GetExecutionExtensionManager(string
{
// Get all extensions for ITestExecutor.
- TestPluginManager.Instance
- .GetTestExtensions(
+ TestPluginManager.GetTestExtensions(
extensionAssembly,
out var unfilteredTestExtensions1,
out var testExtensions1);
// Get all extensions for ITestExecutor2.
- TestPluginManager.Instance
- .GetTestExtensions(
+ TestPluginManager.GetTestExtensions(
extensionAssembly,
out var unfilteredTestExtensions2,
out var testExtensions2);
diff --git a/src/Microsoft.TestPlatform.Common/ExtensionFramework/TestExtensionManager.cs b/src/Microsoft.TestPlatform.Common/ExtensionFramework/TestExtensionManager.cs
index f1be92a674..5f12dd7afa 100644
--- a/src/Microsoft.TestPlatform.Common/ExtensionFramework/TestExtensionManager.cs
+++ b/src/Microsoft.TestPlatform.Common/ExtensionFramework/TestExtensionManager.cs
@@ -128,6 +128,7 @@ public LazyExtension TryGetTestExtension(string extension
break;
}
}
+
return testExtension;
}
diff --git a/src/Microsoft.TestPlatform.Common/ExtensionFramework/TestLoggerExtensionManager.cs b/src/Microsoft.TestPlatform.Common/ExtensionFramework/TestLoggerExtensionManager.cs
index b5fb7b5770..fc5df639bf 100644
--- a/src/Microsoft.TestPlatform.Common/ExtensionFramework/TestLoggerExtensionManager.cs
+++ b/src/Microsoft.TestPlatform.Common/ExtensionFramework/TestLoggerExtensionManager.cs
@@ -53,7 +53,7 @@ protected TestLoggerExtensionManager(
public static TestLoggerExtensionManager Create(IMessageLogger messageLogger)
{
- TestPluginManager.Instance.GetSpecificTestExtensions(
+ TestPluginManager.GetSpecificTestExtensions(
TestPlatformConstants.TestLoggerEndsWithPattern,
out IEnumerable>> unfilteredTestExtensions,
out IEnumerable> filteredTestExtensions);
diff --git a/src/Microsoft.TestPlatform.Common/ExtensionFramework/TestPluginManager.cs b/src/Microsoft.TestPlatform.Common/ExtensionFramework/TestPluginManager.cs
index f89092aaa3..8e3982d41b 100644
--- a/src/Microsoft.TestPlatform.Common/ExtensionFramework/TestPluginManager.cs
+++ b/src/Microsoft.TestPlatform.Common/ExtensionFramework/TestPluginManager.cs
@@ -107,13 +107,13 @@ public static T CreateTestExtension(Type extensionType!!)
///
/// Receives test extensions filtered by Identifier data
///
- public void GetSpecificTestExtensions(
+ public static void GetSpecificTestExtensions(
string endsWithPattern,
out IEnumerable>> unfiltered,
out IEnumerable> filtered) where TMetadata : IMetadata where TPluginInfo : TestPluginInformation
{
var extensions = TestPluginCache.Instance.DiscoverTestExtensions(endsWithPattern);
- GetExtensions(extensions, out unfiltered, out filtered);
+ TestPluginManager.GetExtensions(extensions, out unfiltered, out filtered);
}
///
@@ -142,14 +142,14 @@ public void GetSpecificTestExtensions
/// Skip the extensions cache.
///
- public void GetTestExtensions(
+ public static void GetTestExtensions(
string extensionAssembly,
out IEnumerable>> unfiltered,
out IEnumerable> filtered,
bool skipCache = false) where TMetadata : IMetadata where TPluginInfo : TestPluginInformation
{
var extensions = TestPluginCache.Instance.GetTestExtensions(extensionAssembly, skipCache);
- GetExtensions(extensions, out unfiltered, out filtered);
+ TestPluginManager.GetExtensions(extensions, out unfiltered, out filtered);
}
///
@@ -158,7 +158,7 @@ public void GetTestExtensions(
/// Type of TestPluginIInformation.
/// The dictionary containing plugin identifier data and its info.
/// Collection of test plugins information
- private IEnumerable GetValuesFromDictionary(Dictionary dictionary) where T : TestPluginInformation
+ private static IEnumerable GetValuesFromDictionary(Dictionary dictionary) where T : TestPluginInformation
{
var values = new List();
@@ -193,7 +193,7 @@ private IEnumerable GetValuesFromDictionary(Dictionary
///
/// Receives test extensions filtered by Identifier data
///
- private void GetExtensions(
+ private static void GetExtensions(
Dictionary testPluginInfo,
out IEnumerable>> unfiltered,
out IEnumerable> filtered) where TMetadata : IMetadata where TPluginInfo : TestPluginInformation
@@ -201,7 +201,7 @@ private void GetExtensions(
var unfilteredExtensions = new List>>();
var filteredExtensions = new List>();
- var testPlugins = GetValuesFromDictionary(testPluginInfo);
+ var testPlugins = TestPluginManager.GetValuesFromDictionary(testPluginInfo);
foreach (var plugin in testPlugins)
{
if (!string.IsNullOrEmpty(plugin.IdentifierData))
diff --git a/src/Microsoft.TestPlatform.Common/Hosting/TestRunTimeExtensionManager.cs b/src/Microsoft.TestPlatform.Common/Hosting/TestRunTimeExtensionManager.cs
index de3809caca..7372fb487c 100644
--- a/src/Microsoft.TestPlatform.Common/Hosting/TestRunTimeExtensionManager.cs
+++ b/src/Microsoft.TestPlatform.Common/Hosting/TestRunTimeExtensionManager.cs
@@ -54,7 +54,7 @@ protected TestRuntimeExtensionManager(
///
public static TestRuntimeExtensionManager Create(IMessageLogger messageLogger)
{
- TestPluginManager.Instance.GetSpecificTestExtensions(
+ TestPluginManager.GetSpecificTestExtensions(
TestPlatformConstants.RunTimeEndsWithPattern,
out IEnumerable>> unfilteredTestExtensions,
out IEnumerable> filteredTestExtensions);
diff --git a/src/Microsoft.TestPlatform.Common/SettingsProvider/SettingsProviderExtensionManager.cs b/src/Microsoft.TestPlatform.Common/SettingsProvider/SettingsProviderExtensionManager.cs
index 809b84936a..8c7a8cccf1 100644
--- a/src/Microsoft.TestPlatform.Common/SettingsProvider/SettingsProviderExtensionManager.cs
+++ b/src/Microsoft.TestPlatform.Common/SettingsProvider/SettingsProviderExtensionManager.cs
@@ -106,8 +106,7 @@ public static SettingsProviderExtensionManager Create()
if (s_settingsProviderExtensionManager == null)
{
- TestPluginManager.Instance
- .GetSpecificTestExtensions(
+ TestPluginManager.GetSpecificTestExtensions(
TestPlatformConstants.TestAdapterEndsWithPattern,
out var unfilteredTestExtensions,
out var testExtensions);
diff --git a/src/Microsoft.TestPlatform.Common/Utilities/RunSettingsUtilities.cs b/src/Microsoft.TestPlatform.Common/Utilities/RunSettingsUtilities.cs
index 1ecc3ffa20..9a66fbc8de 100644
--- a/src/Microsoft.TestPlatform.Common/Utilities/RunSettingsUtilities.cs
+++ b/src/Microsoft.TestPlatform.Common/Utilities/RunSettingsUtilities.cs
@@ -190,4 +190,15 @@ public static IEnumerable GetTestAdaptersPaths(string runSettings)
return testAdaptersPaths;
}
+ ///
+ /// Gets the test adapter loading strategy
+ ///
+ /// Test run settings
+ /// Test adapter loading strategy
+ internal static TestAdapterLoadingStrategy GetLoadingStrategy(string runSettings)
+ {
+ var runConfiguration = XmlRunSettingsUtilities.GetRunConfigurationNode(runSettings);
+
+ return runConfiguration.TestAdapterLoadingStrategy;
+ }
}
diff --git a/src/Microsoft.TestPlatform.ObjectModel/Friends.cs b/src/Microsoft.TestPlatform.ObjectModel/Friends.cs
index 1261d15c99..1eabaf534d 100644
--- a/src/Microsoft.TestPlatform.ObjectModel/Friends.cs
+++ b/src/Microsoft.TestPlatform.ObjectModel/Friends.cs
@@ -8,8 +8,11 @@
[assembly: InternalsVisibleTo("Microsoft.VisualStudio.TestPlatform.Extensions.MSPhoneAdapter, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")]
[assembly: InternalsVisibleTo("datacollector, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")]
[assembly: InternalsVisibleTo("Microsoft.VisualStudio.TestPlatform.Common, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")]
+[assembly: InternalsVisibleTo("vstest.console, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")]
+[assembly: InternalsVisibleTo("Microsoft.VisualStudio.TestPlatform.Client, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")]
[assembly: InternalsVisibleTo("Microsoft.TestPlatform.ObjectModel.UnitTests, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")]
[assembly: InternalsVisibleTo("datacollector.UnitTests, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")]
[assembly: InternalsVisibleTo("Microsoft.TestPlatform.Extensions.EventLogCollector.UnitTests, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")]
[assembly: InternalsVisibleTo("Microsoft.TestPlatform.ObjectModel.ManagedNameUtilities.UnitTests, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")]
+[assembly: InternalsVisibleTo("vstest.console.UnitTests, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")]
diff --git a/src/Microsoft.TestPlatform.ObjectModel/RunSettings/RunConfiguration.cs b/src/Microsoft.TestPlatform.ObjectModel/RunSettings/RunConfiguration.cs
index 18ed049720..165a249cf7 100644
--- a/src/Microsoft.TestPlatform.ObjectModel/RunSettings/RunConfiguration.cs
+++ b/src/Microsoft.TestPlatform.ObjectModel/RunSettings/RunConfiguration.cs
@@ -211,6 +211,11 @@ public bool DisableAppDomain
}
}
+ ///
+ /// Gets or sets the test adapter loading strategy.
+ ///
+ internal TestAdapterLoadingStrategy TestAdapterLoadingStrategy { get; set; }
+
///
/// Gets a value indicating whether parallelism needs to be disabled by the adapters.
///
@@ -323,11 +328,7 @@ public string TestAdaptersPaths
/// Gets or sets the execution thread apartment state.
///
[CLSCompliant(false)]
- public PlatformApartmentState ExecutionThreadApartmentState
- {
- get;
- set;
- }
+ public PlatformApartmentState ExecutionThreadApartmentState { get; set; }
///
/// Gets or sets a value indicating whether to treat the errors from test adapters as warnings.
@@ -501,6 +502,13 @@ public override XmlElement ToXml()
root.AppendChild(testAdaptersPaths);
}
+ if (this.TestAdapterLoadingStrategy != TestAdapterLoadingStrategy.Default)
+ {
+ XmlElement adapterLoadingStrategy = doc.CreateElement("TestAdapterLoadingStrategy");
+ adapterLoadingStrategy.InnerXml = this.TestAdapterLoadingStrategy.ToString();
+ root.AppendChild(adapterLoadingStrategy);
+ }
+
XmlElement treatTestAdapterErrorsAsWarnings = doc.CreateElement("TreatTestAdapterErrorsAsWarnings");
treatTestAdapterErrorsAsWarnings.InnerXml = TreatTestAdapterErrorsAsWarnings.ToString();
root.AppendChild(treatTestAdapterErrorsAsWarnings);
@@ -761,6 +769,21 @@ public static RunConfiguration FromXml(XmlReader reader)
runConfiguration.TestAdaptersPaths = reader.ReadElementContentAsString();
break;
+ case "TestAdapterLoadingStrategy":
+ XmlRunSettingsUtilities.ThrowOnHasAttributes(reader);
+ value = reader.ReadElementContentAsString();
+ if (Enum.TryParse(value, out var loadingStrategy))
+ {
+ runConfiguration.TestAdapterLoadingStrategy = loadingStrategy;
+ }
+ else
+ {
+ throw new SettingsException(string.Format(CultureInfo.CurrentCulture,
+ Resources.Resources.InvalidSettingsIncorrectValue, Constants.RunConfigurationSettingsName, value, elementName));
+ }
+
+ break;
+
case "TreatTestAdapterErrorsAsWarnings":
XmlRunSettingsUtilities.ThrowOnHasAttributes(reader);
bool treatTestAdapterErrorsAsWarnings = false;
diff --git a/src/Microsoft.TestPlatform.ObjectModel/TestAdapterLoadingStrategy.cs b/src/Microsoft.TestPlatform.ObjectModel/TestAdapterLoadingStrategy.cs
new file mode 100644
index 0000000000..14465435cc
--- /dev/null
+++ b/src/Microsoft.TestPlatform.ObjectModel/TestAdapterLoadingStrategy.cs
@@ -0,0 +1,46 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+namespace Microsoft.VisualStudio.TestPlatform.ObjectModel;
+
+using System;
+
+///
+/// Represents a loading strategy
+///
+[Flags]
+internal enum TestAdapterLoadingStrategy
+{
+ ///
+ /// A strategy not defined, Test Platform will load adapters normally.
+ ///
+ Default = 0,
+
+ ///
+ /// Test Platform will only load adapters specified by /TestAdapterPath (or RunConfiguration.TestAdaptersPaths node).
+ /// If a specific adapter path is provided, adapter will be loaded; if a directory path is provided adapters directly in that folder will be loaded.
+ /// If no adapter path is specified, test run will fail.
+ /// This will imply /InIsolation switch and force the tests to be run in an isolated process.
+ ///
+ Explicit = 1,
+
+ ///
+ /// Load adapters next to source.
+ ///
+ NextToSource = 2,
+
+ ///
+ /// Default runtime providers inside Extensions folder will be included.
+ ///
+ DefaultRuntimeProviders = 4,
+
+ ///
+ /// Load adapters inside Extensions folder.
+ ///
+ ExtensionsDirectory = 8,
+
+ ///
+ /// Directory wide searches will be recursive, this is required to be used with or .
+ ///
+ Recursive = 16,
+}
diff --git a/src/vstest.console/CommandLine/CommandLineOptions.cs b/src/vstest.console/CommandLine/CommandLineOptions.cs
index 443318c3f7..bbaccb8909 100644
--- a/src/vstest.console/CommandLine/CommandLineOptions.cs
+++ b/src/vstest.console/CommandLine/CommandLineOptions.cs
@@ -121,7 +121,12 @@ public IEnumerable Sources
///
/// Path to the custom test adapters.
///
- public string TestAdapterPath { get; set; }
+ public string[] TestAdapterPath { get; set; }
+
+ ///
+ /// Test adapter loading strategy.
+ ///
+ public TestAdapterLoadingStrategy TestAdapterLoadingStrategy { get; set; }
///
/// Process Id of the process which launched vstest runner
@@ -176,13 +181,9 @@ public IEnumerable Sources
///
/// Specifies whether the target device has a Windows Phone context or not
///
- public bool HasPhoneContext
- {
- get
- {
- return !string.IsNullOrEmpty(TargetDevice);
- }
- }
+ public bool HasPhoneContext => !string.IsNullOrEmpty(TargetDevice);
+
+ public bool TestAdapterPathsSet => (TestAdapterPath?.Length ?? 0) != 0;
///
/// Specifies the target platform type for test run.
diff --git a/src/vstest.console/CommandLine/Executor.cs b/src/vstest.console/CommandLine/Executor.cs
index 44c1e5692b..00975f5b29 100644
--- a/src/vstest.console/CommandLine/Executor.cs
+++ b/src/vstest.console/CommandLine/Executor.cs
@@ -222,7 +222,16 @@ private int GetArgumentProcessors(string[] args, out List pr
// Examples: processors to enable loggers that are statically configured, and to start logging,
// should always be executed.
var processorsToAlwaysExecute = processorFactory.GetArgumentProcessorsToAlwaysExecute();
- processors.AddRange(processorsToAlwaysExecute);
+ foreach (var processor in processorsToAlwaysExecute)
+ {
+ if (processors.Any(i => i.Metadata.Value.CommandName == processor.Metadata.Value.CommandName))
+ {
+ continue;
+ }
+
+ // We need to initialize the argument executor if it's set to always execute. This ensures it will be initialized with other executors.
+ processors.Add(ArgumentProcessorFactory.WrapLazyProcessorToInitializeOnInstantiation(processor));
+ }
// Initialize Runsettings with defaults
RunSettingsManager.Instance.AddDefaultRunSettings();
diff --git a/src/vstest.console/Processors/RunSpecificTestsArgumentProcessor.cs b/src/vstest.console/Processors/RunSpecificTestsArgumentProcessor.cs
index 15e1828c0e..adbed0ec90 100644
--- a/src/vstest.console/Processors/RunSpecificTestsArgumentProcessor.cs
+++ b/src/vstest.console/Processors/RunSpecificTestsArgumentProcessor.cs
@@ -274,7 +274,7 @@ private void ExecuteSelectedTests()
// No tests were discovered from the given sources.
warningMessage = string.Format(CultureInfo.CurrentUICulture, CommandLineResources.NoTestsAvailableInSources, string.Join(", ", _commandLineOptions.Sources));
- if (string.IsNullOrEmpty(_commandLineOptions.TestAdapterPath))
+ if (!_commandLineOptions.TestAdapterPathsSet)
{
warningMessage = string.Format(CultureInfo.CurrentCulture, CommandLineResources.StringFormatToJoinTwoStrings, warningMessage, CommandLineResources.SuggestTestAdapterPathIfNoTestsIsFound);
}
@@ -376,7 +376,7 @@ private void TestRunRequest_OnRunCompletion(object sender, TestRunCompleteEventA
var testsFoundInAnySource = e.TestRunStatistics != null && (e.TestRunStatistics.ExecutedTests > 0);
// Indicate the user to use testadapterpath command if there are no tests found
- if (!testsFoundInAnySource && string.IsNullOrEmpty(CommandLineOptions.Instance.TestAdapterPath) && _commandLineOptions.TestCaseFilterValue == null)
+ if (!testsFoundInAnySource && !CommandLineOptions.Instance.TestAdapterPathsSet && _commandLineOptions.TestCaseFilterValue == null)
{
_output.Warning(false, CommandLineResources.SuggestTestAdapterPathIfNoTestsIsFound);
}
diff --git a/src/vstest.console/Processors/RunTestsArgumentProcessor.cs b/src/vstest.console/Processors/RunTestsArgumentProcessor.cs
index f384f7914f..42972f46de 100644
--- a/src/vstest.console/Processors/RunTestsArgumentProcessor.cs
+++ b/src/vstest.console/Processors/RunTestsArgumentProcessor.cs
@@ -241,7 +241,7 @@ private void TestRunRequest_OnRunCompletion(object sender, TestRunCompleteEventA
var testsFoundInAnySource = e.TestRunStatistics != null && (e.TestRunStatistics.ExecutedTests > 0);
// Indicate the user to use test adapter path command if there are no tests found
- if (!testsFoundInAnySource && string.IsNullOrEmpty(CommandLineOptions.Instance.TestAdapterPath) && _commandLineOptions.TestCaseFilterValue == null)
+ if (!testsFoundInAnySource && !CommandLineOptions.Instance.TestAdapterPathsSet && _commandLineOptions.TestCaseFilterValue == null)
{
_output.Warning(false, CommandLineResources.SuggestTestAdapterPathIfNoTestsIsFound);
}
diff --git a/src/vstest.console/Processors/TestAdapterLoadingStrategyArgumentProcessor.cs b/src/vstest.console/Processors/TestAdapterLoadingStrategyArgumentProcessor.cs
new file mode 100644
index 0000000000..4785cfe395
--- /dev/null
+++ b/src/vstest.console/Processors/TestAdapterLoadingStrategyArgumentProcessor.cs
@@ -0,0 +1,253 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+#nullable disable
+
+namespace Microsoft.VisualStudio.TestPlatform.CommandLine.Processors;
+
+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;
+
+///
+/// Allows the user to specify a order of loading custom adapters from.
+///
+internal class TestAdapterLoadingStrategyArgumentProcessor : IArgumentProcessor
+{
+ ///
+ /// The name of the command line argument that the TestAdapterLoadingStrategyArgumentProcessor handles.
+ ///
+ public const string CommandName = "/TestAdapterLoadingStrategy";
+
+ private Lazy _metadata;
+
+ private Lazy _executor;
+
+ ///
+ /// Gets the metadata.
+ ///
+ public Lazy Metadata
+ {
+ get
+ {
+ if (_metadata == null)
+ {
+ _metadata = new Lazy(() => new TestAdapterLoadingStrategyArgumentProcessorCapabilities());
+ }
+
+ return _metadata;
+ }
+ }
+
+ ///
+ /// Gets or sets the executor.
+ ///
+ public Lazy Executor
+ {
+ get
+ {
+ if (_executor == null)
+ {
+ _executor = new Lazy(() => new TestAdapterLoadingStrategyArgumentExecutor(CommandLineOptions.Instance, RunSettingsManager.Instance, ConsoleOutput.Instance, new FileHelper()));
+ }
+
+ return _executor;
+ }
+
+ set
+ {
+ _executor = value;
+ }
+ }
+}
+
+///
+/// The argument capabilities.
+///
+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;
+}
+
+///
+/// The argument executor.
+///
+internal class TestAdapterLoadingStrategyArgumentExecutor : IArgumentExecutor
+{
+ ///
+ /// Used for getting sources.
+ ///
+ private readonly CommandLineOptions _commandLineOptions;
+
+ ///
+ /// Run settings provider.
+ ///
+ private readonly IRunSettingsProvider _runSettingsManager;
+
+ ///
+ /// Used for sending output.
+ ///
+ private readonly IOutput _output;
+
+ ///
+ /// For file related operation
+ ///
+ private readonly IFileHelper _fileHelper;
+
+ public const string RunSettingsPath = "RunConfiguration.TestAdapterLoadingStrategy";
+
+ ///
+ /// Default constructor.
+ ///
+ /// The options.
+ /// The test platform
+ public TestAdapterLoadingStrategyArgumentExecutor(CommandLineOptions options!!, IRunSettingsProvider runSettingsManager!!, IOutput output!!, IFileHelper fileHelper!!)
+ {
+ _commandLineOptions = options;
+ _runSettingsManager = runSettingsManager;
+ _output = output;
+ _fileHelper = fileHelper;
+ }
+
+ #region IArgumentExecutor
+ ///
+ /// Initializes with the argument that was provided with the command.
+ ///
+ /// Argument that was provided with the command.
+ 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);
+ }
+
+ ///
+ /// Executes the argument processor.
+ ///
+ /// The .
+ 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 (string.IsNullOrWhiteSpace(value))
+ {
+ 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 ?? new string[0];
+ 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();
+ }
+ }
+}
diff --git a/src/vstest.console/Processors/TestAdapterPathArgumentProcessor.cs b/src/vstest.console/Processors/TestAdapterPathArgumentProcessor.cs
index 18bdefb83e..d273ec55e4 100644
--- a/src/vstest.console/Processors/TestAdapterPathArgumentProcessor.cs
+++ b/src/vstest.console/Processors/TestAdapterPathArgumentProcessor.cs
@@ -7,9 +7,7 @@ namespace Microsoft.VisualStudio.TestPlatform.CommandLine.Processors;
using System;
using System.Collections.Generic;
-using System.Diagnostics.Contracts;
using System.Globalization;
-using System.IO;
using System.Linq;
using Common;
@@ -101,24 +99,23 @@ internal class TestAdapterPathArgumentExecutor : IArgumentExecutor
///
/// Separators for multiple paths in argument.
///
- private readonly char[] _argumentSeparators = new[] { ';' };
+ internal readonly static char[] ArgumentSeparators = new[] { ';' };
+
+ public const string RunSettingsPath = "RunConfiguration.TestAdaptersPaths";
///
/// Default constructor.
///
/// The options.
/// The test platform
- public TestAdapterPathArgumentExecutor(CommandLineOptions options, IRunSettingsProvider runSettingsManager, IOutput output, IFileHelper fileHelper)
+ public TestAdapterPathArgumentExecutor(CommandLineOptions options!!, IRunSettingsProvider runSettingsManager!!, IOutput output!!, IFileHelper fileHelper!!)
{
- Contract.Requires(options != null);
-
_commandLineOptions = options;
_runSettingsManager = runSettingsManager;
_output = output;
_fileHelper = fileHelper;
}
-
#region IArgumentExecutor
///
@@ -127,71 +124,34 @@ public TestAdapterPathArgumentExecutor(CommandLineOptions options, IRunSettingsP
/// Argument that was provided with the command.
public void Initialize(string argument)
{
- string invalidAdapterPathArgument = argument;
-
if (string.IsNullOrWhiteSpace(argument))
{
throw new CommandLineException(
string.Format(CultureInfo.CurrentCulture, CommandLineResources.TestAdapterPathValueRequired));
}
- string customAdaptersPath;
-
- try
- {
- var testAdapterPaths = new List();
- var testAdapterFullPaths = new List();
-
- // VSTS task add double quotes around TestAdapterpath. For example if user has given TestAdapter path C:\temp,
- // Then VSTS task will add TestAdapterPath as "/TestAdapterPath:\"C:\Temp\"".
- // Remove leading and trailing ' " ' chars...
- argument = argument.Trim().Trim(new char[] { '\"' });
-
- // Get test adapter paths from RunSettings.
- var testAdapterPathsInRunSettings = _runSettingsManager.QueryRunSettingsNode("RunConfiguration.TestAdaptersPaths");
+ string[] customAdaptersPath;
- if (!string.IsNullOrWhiteSpace(testAdapterPathsInRunSettings))
- {
- testAdapterPaths.AddRange(SplitPaths(testAdapterPathsInRunSettings));
- }
+ var testAdapterPaths = new List();
- testAdapterPaths.AddRange(SplitPaths(argument));
+ // VSTS task add double quotes around TestAdapterpath. For example if user has given TestAdapter path C:\temp,
+ // Then VSTS task will add TestAdapterPath as "/TestAdapterPath:\"C:\Temp\"".
+ // Remove leading and trailing ' " ' chars...
+ argument = argument.Trim().Trim(new char[] { '\"' });
- foreach (var testAdapterPath in testAdapterPaths)
- {
- // TestAdaptersPaths could contain environment variables
- var testAdapterFullPath = Path.GetFullPath(Environment.ExpandEnvironmentVariables(testAdapterPath));
+ // Get test adapter paths from RunSettings.
+ var testAdapterPathsInRunSettings = _runSettingsManager.QueryRunSettingsNode(RunSettingsPath);
- if (!_fileHelper.DirectoryExists(testAdapterFullPath))
- {
- invalidAdapterPathArgument = testAdapterPath;
- throw new DirectoryNotFoundException(CommandLineResources.TestAdapterPathDoesNotExist);
- }
-
- testAdapterFullPaths.Add(testAdapterFullPath);
- }
-
- customAdaptersPath = string.Join(";", testAdapterFullPaths.Distinct().ToArray());
-
- _runSettingsManager.UpdateRunSettingsNode("RunConfiguration.TestAdaptersPaths", customAdaptersPath);
- }
- catch (Exception e)
+ if (!string.IsNullOrWhiteSpace(testAdapterPathsInRunSettings))
{
- throw new CommandLineException(
- string.Format(CultureInfo.CurrentCulture, CommandLineResources.InvalidTestAdapterPathCommand, invalidAdapterPathArgument, e.Message));
+ testAdapterPaths.AddRange(SplitPaths(testAdapterPathsInRunSettings));
}
- _commandLineOptions.TestAdapterPath = customAdaptersPath;
- }
+ testAdapterPaths.AddRange(SplitPaths(argument));
+ customAdaptersPath = testAdapterPaths.Distinct().ToArray();
- ///
- /// Splits provided paths into array.
- ///
- /// Source paths joined by semicolons.
- /// Paths.
- private string[] SplitPaths(string paths)
- {
- return string.IsNullOrWhiteSpace(paths) ? (new string[] { }) : paths.Split(_argumentSeparators, StringSplitOptions.RemoveEmptyEntries);
+ _runSettingsManager.UpdateRunSettingsNode(RunSettingsPath, string.Join(";", customAdaptersPath));
+ _commandLineOptions.TestAdapterPath = customAdaptersPath;
}
///
@@ -203,6 +163,15 @@ public ArgumentProcessorResult Execute()
// Nothing to do since we updated the parameter during initialize parameter
return ArgumentProcessorResult.Success;
}
-
#endregion
+
+ ///
+ /// Splits provided paths into array.
+ ///
+ /// Source paths joined by semicolons.
+ /// Paths.
+ internal static string[] SplitPaths(string paths)
+ {
+ return string.IsNullOrWhiteSpace(paths) ? new string[0] : paths.Split(ArgumentSeparators, StringSplitOptions.RemoveEmptyEntries);
+ }
}
diff --git a/src/vstest.console/Processors/Utilities/ArgumentProcessorFactory.cs b/src/vstest.console/Processors/Utilities/ArgumentProcessorFactory.cs
index b99afa6182..085645445a 100644
--- a/src/vstest.console/Processors/Utilities/ArgumentProcessorFactory.cs
+++ b/src/vstest.console/Processors/Utilities/ArgumentProcessorFactory.cs
@@ -206,6 +206,7 @@ public IEnumerable GetArgumentProcessorsToAlwaysExecute()
new RunTestsArgumentProcessor(),
new RunSpecificTestsArgumentProcessor(),
new TestAdapterPathArgumentProcessor(),
+ new TestAdapterLoadingStrategyArgumentProcessor(),
new TestCaseFilterArgumentProcessor(),
new ParentProcessIdArgumentProcessor(),
new PortArgumentProcessor(),
@@ -273,9 +274,7 @@ private void BuildCommandMaps()
/// The lazy processor.
/// The argument with which the real processor should be initialized.
/// The decorated lazy processor.
- private static IArgumentProcessor WrapLazyProcessorToInitializeOnInstantiation(
- IArgumentProcessor processor,
- string initArg = null)
+ public static IArgumentProcessor WrapLazyProcessorToInitializeOnInstantiation(IArgumentProcessor processor, string initArg = null)
{
var processorExecutor = processor.Executor;
var lazyArgumentProcessor = new Lazy(() =>
diff --git a/src/vstest.console/Processors/Utilities/ArgumentProcessorPriority.cs b/src/vstest.console/Processors/Utilities/ArgumentProcessorPriority.cs
index 318c1f7e9c..3407defb4a 100644
--- a/src/vstest.console/Processors/Utilities/ArgumentProcessorPriority.cs
+++ b/src/vstest.console/Processors/Utilities/ArgumentProcessorPriority.cs
@@ -45,10 +45,18 @@ internal enum ArgumentProcessorPriority
///
/// Priority of TestAdapterPathArgumentProcessor.
+ ///
/// The priority of TestAdapterPath processor is more than the logger because logger initialization
/// loads the extensions which are incomplete if custom test adapter is enabled
///
- TestAdapterPath = 10,
+ TestAdapterPath = 9,
+
+ ///
+ /// Priority of TestAdapterLoadingStrategyArgumentProcessor.
+ ///
+ /// This needs to be higher than most of other arguments, because it affects where we look for test adapters.
+ ///
+ TestAdapterLoadingStrategy = 10,
///
/// Priority of processors that needs to update runsettings.
diff --git a/src/vstest.console/Processors/Utilities/HelpContentPriority.cs b/src/vstest.console/Processors/Utilities/HelpContentPriority.cs
index 1bf11b14ba..fc7c401753 100644
--- a/src/vstest.console/Processors/Utilities/HelpContentPriority.cs
+++ b/src/vstest.console/Processors/Utilities/HelpContentPriority.cs
@@ -24,6 +24,7 @@ namespace Microsoft.VisualStudio.TestPlatform.CommandLine.Processors;
/// --ListTests
/// --Parallel
/// --TestAdapterPath
+/// --TestAdapterLoadingStrategy
///
/// Diagnose/Report
/// --Diag
@@ -35,7 +36,7 @@ namespace Microsoft.VisualStudio.TestPlatform.CommandLine.Processors;
/// --Port
///
/// Help
-/// -Help
+/// --Help
///
internal enum HelpContentPriority
{
@@ -99,6 +100,11 @@ internal enum HelpContentPriority
///
TestAdapterPathArgumentProcessorHelpPriority,
+ ///
+ /// TestAdapterLoadingStrategyArgumentProcessor Help
+ ///
+ TestAdapterLoadingStrategyArgumentProcessorHelpPriority,
+
///
/// EnableDiagArgumentProcessor Help
///
diff --git a/src/vstest.console/Resources/Resources.Designer.cs b/src/vstest.console/Resources/Resources.Designer.cs
index 831dd46323..3ebb595467 100644
--- a/src/vstest.console/Resources/Resources.Designer.cs
+++ b/src/vstest.console/Resources/Resources.Designer.cs
@@ -1516,6 +1516,39 @@ internal static string SwitchToNoIsolation {
}
}
+ ///
+ /// Looks up a localized string similar to --TestAdapterLoadingStrategy|/TestAdapterLoadingStrategy:<strategy>
+ /// This affects adapter loading behavior.
+ ///
+ /// Currently supported behaviours:
+ /// - Explicit: Test Plarform will only load adapters specified by /TestAdapterPath (or RunConfiguration.TestAdaptersPaths node).
+ /// If a specific adapter path is provided, adapter will be loaded; if a directory path is provided adapters directly in that folder will be loaded, unless Recursive option is also specified.
+ /// [rest of string was truncated]";.
+ ///
+ internal static string TestAdapterLoadingStrategyHelp {
+ get {
+ return ResourceManager.GetString("TestAdapterLoadingStrategyHelp", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Specified value ({0}) for /TestAdapterLoadingStrategy is invalid!.
+ ///
+ internal static string TestAdapterLoadingStrategyValueInvalid {
+ get {
+ return ResourceManager.GetString("TestAdapterLoadingStrategyValueInvalid", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to "Recursive" adapter loading strategy is cannot be used by itself. Please specify at least one of: {0}.
+ ///
+ internal static string TestAdapterLoadingStrategyValueInvalidRecursive {
+ get {
+ return ResourceManager.GetString("TestAdapterLoadingStrategyValueInvalidRecursive", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to The custom test adapter search path provided was not found, provide a valid path and try again..
///
@@ -1546,6 +1579,15 @@ internal static string TestAdapterPathValueRequired {
}
}
+ ///
+ /// Looks up a localized string similar to The /TestAdapterPath parameter needs to be provided when "{0}" test adapter loading strategy is specified!.
+ ///
+ internal static string TestAdapterPathValueRequiredWhenStrategyXIsUsed {
+ get {
+ return ResourceManager.GetString("TestAdapterPathValueRequiredWhenStrategyXIsUsed", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to --TestCaseFilter|/TestCaseFilter:<Expression>
/// Run tests that match the given expression.
diff --git a/src/vstest.console/Resources/Resources.resx b/src/vstest.console/Resources/Resources.resx
index 0997bea995..a14635b564 100644
--- a/src/vstest.console/Resources/Resources.resx
+++ b/src/vstest.console/Resources/Resources.resx
@@ -560,6 +560,43 @@
The /TestAdapterPath parameter requires a value, which is path of a location containing custom test adapters. Example: /TestAdapterPath:c:\MyCustomAdapters
+
+ --TestAdapterLoadingStrategy|/TestAdapterLoadingStrategy:<strategy>
+ This affects adapter loading behavior.
+
+ Currently supported behaviors:
+ - Explicit: Test Platform will only load adapters specified by /TestAdapterPath (or RunConfiguration.TestAdaptersPaths node).
+ If a specific adapter path is provided, adapter will be loaded; if a directory path is provided adapters directly in that folder will be loaded, unless Recursive option is also specified.
+ If no adapter path is specified, test run will fail.
+ This will imply /InIsolation switch and force the tests to be run in an isolated process.
+
+ - Default: Test Platform will load adapters is if this argument has not been specified.
+ It will pick up extensions from next to source, provided additional adapter paths and from the default directory.
+
+ - DefaultRuntimeProviders: Load default runtime providers shipped with Test Platform.
+ If this is not specified when "Explicit" option is set, a test host provider need to be specified explicitly.
+
+ - ExtensionsDirectory: Load adapters inside Extensions folder.
+
+ - NextToSource: Load adapters next to source.
+
+ - Recursive: Recursively search folders when loading adapters. This requires "Explicit" or "NextToSource" to be specified too.
+ Do not translate "Default", "DefaultRuntimeProviders", "ExtensionsDirectory", "NextToSource" or "Recursive".
+
+
+ Specified value ({0}) for /TestAdapterLoadingStrategy is invalid!
+
+
+ Recursive adapter loading strategy is cannot be used by itself. Please combine with one or more of: {0}
+
+ - Do not translate "Recursive",
+ - {0} is the strategy names, seperated by comma: for example "Explicit, NextToSource"
+
+
+
+ The /TestAdapterPath parameter needs to be provided when "{0}" test adapter loading strategy is specified!
+ {0} is the strategy name, "Explicit" for example.
+
--TestCaseFilter|/TestCaseFilter:<Expression>
Run tests that match the given expression.
diff --git a/src/vstest.console/Resources/xlf/Resources.cs.xlf b/src/vstest.console/Resources/xlf/Resources.cs.xlf
index 0c842e4e07..e5a20355f2 100644
--- a/src/vstest.console/Resources/xlf/Resources.cs.xlf
+++ b/src/vstest.console/Resources/xlf/Resources.cs.xlf
@@ -1113,6 +1113,68 @@
Neplatné testSessionCorrelationId
+
+
+ --TestAdapterLoadingStrategy|/TestAdapterLoadingStrategy:<strategy>
+ This affects adapter loading behavior.
+
+ Currently supported behaviours:
+ - Explicit: Test Plarform will only load adapters specified by /TestAdapterPath (or RunConfiguration.TestAdaptersPaths node).
+ If a specific adapter path is provided, adapter will be loaded; if a directory path is provided adapters directly in that folder will be loaded, unless Recursive option is also specified.
+ If no adapter path is specified, test run will fail.
+ This will imply /InIsolation switch and force the tests to be run in an isolated process.
+
+ - Default: Test Platfrom will load adapters is if this argument has not beed specified.
+ It will pick up extensions from next to source, provided aditional adapter paths and from the default directory.
+
+ - DefaultRuntimeProviders: Load default runtime providers shipped with Test Platform.
+ If this is not specified when "Explicit" option is set, a test host provider need to be specified explicitly.
+
+ - ExtensionsDirectory: Load adapters inside Extensions folder.
+
+ - NextToSource: Load adapters next to source.
+
+ - Recursive: Recursively search folders when loading adapters. This requires "Explicit" or "NextToSource" to be specified too.
+
+ Do not translate "Default", "DefaultRuntimeProviders", "ExtensionsDirectory", "NextToSource" or "Recursive".
+
+
+
+ Specified value ({0}) for /TestAdapterLoadingStrategy is invalid!
+
+
+
+
+ Recursive adapter loading strategy is cannot be used by itself. Please combine with one or more of: {0}
+
+ - Do not translate "Recursive",
+ - {0} is the strategy names, seperated by comma: for example "Explicit, NextToSource"
+
+
+
+
+ The /TestAdapterPath parameter needs to be provided when "{0}" test adapter loading strategy is specified!
+ {0} is the strategy name, "Explicit" for example.
+