diff --git a/playground/MSTest1/MSTest1.csproj b/playground/MSTest1/MSTest1.csproj
index c1a72605a4..a8f5883dae 100644
--- a/playground/MSTest1/MSTest1.csproj
+++ b/playground/MSTest1/MSTest1.csproj
@@ -6,9 +6,10 @@
- $(TargetFrameworks);net472
+ $(TargetFrameworks);net472;net5.0$(TargetFrameworks);net451
+ falsefalse
diff --git a/playground/MSTest1/UnitTest1.cs b/playground/MSTest1/UnitTest1.cs
index 4ae9e815df..aa92d18eef 100644
--- a/playground/MSTest1/UnitTest1.cs
+++ b/playground/MSTest1/UnitTest1.cs
@@ -13,5 +13,6 @@ public class UnitTest1
[TestMethod]
public void TestMethod1()
{
+ // Thread.Sleep(1000);
}
}
diff --git a/playground/TestPlatform.Playground/Program.cs b/playground/TestPlatform.Playground/Program.cs
index 9b8486ed55..184a5f28fc 100644
--- a/playground/TestPlatform.Playground/Program.cs
+++ b/playground/TestPlatform.Playground/Program.cs
@@ -39,39 +39,70 @@ static void Main(string[] args)
var playground = Path.GetFullPath(Path.Combine(here, "..", "..", "..", ".."));
var console = Path.Combine(here, "vstest.console", "vstest.console.exe");
- var consoleOptions = new ConsoleParameters
- {
- LogFilePath = Path.Combine(here, "logs", "log.txt"),
- TraceLevel = TraceLevel.Verbose,
- };
- var r = new VsTestConsoleWrapper(console, consoleOptions);
var sourceSettings = @"
true
- 0
+ 4
";
+
var sources = new[] {
- Path.Combine(playground, "MSTest1", "bin", "Debug", "net472", "MSTest1.dll")
+ Path.Combine(playground, "MSTest1", "bin", "Debug", "net472", "MSTest1.dll"),
+ Path.Combine(playground, "MSTest1", "bin", "Debug", "net5.0", "MSTest1.dll"),
+ @"C:\Users\jajares\source\repos\TestProject48\TestProject48\bin\Debug\net48\TestProject48.dll",
+ @"C:\Users\jajares\source\repos\TestProject48\TestProject1\bin\Debug\net48\win10-x64\TestProject1.dll"
};
+ // console mode
+ var settingsFile = Path.GetTempFileName();
+ try
+ {
+ File.WriteAllText(settingsFile, sourceSettings);
+ var process = Process.Start(console, string.Join(" ", sources) + " --settings:" + settingsFile + " --listtests");
+ process.WaitForExit();
+ if (process.ExitCode != 0)
+ {
+ throw new Exception($"Process failed with {process.ExitCode}");
+ }
+ }
+ finally
+ {
+ try { File.Delete(settingsFile); } catch { }
+ }
+
+ // design mode
+ var consoleOptions = new ConsoleParameters
+ {
+ LogFilePath = Path.Combine(here, "logs", "log.txt"),
+ TraceLevel = TraceLevel.Verbose,
+ };
var options = new TestPlatformOptions();
- r.RunTestsWithCustomTestHost(sources, sourceSettings, options, new TestRunHandler(), new DebuggerTestHostLauncher());
+ var r = new VsTestConsoleWrapper(console, consoleOptions);
+ var sessionHandler = new TestSessionHandler();
+#pragma warning disable CS0618 // Type or member is obsolete
+ r.StartTestSession(sources, sourceSettings, sessionHandler);
+#pragma warning restore CS0618 // Type or member is obsolete
+ var discoveryHandler = new PlaygroundTestDiscoveryHandler();
+ r.DiscoverTests(sources, sourceSettings, options, sessionHandler.TestSessionInfo, discoveryHandler);
+ r.RunTestsWithCustomTestHost(discoveryHandler.TestCases, sourceSettings, options, sessionHandler.TestSessionInfo, new TestRunHandler(), new DebuggerTestHostLauncher());
}
public class PlaygroundTestDiscoveryHandler : ITestDiscoveryEventsHandler, ITestDiscoveryEventsHandler2
{
private int _testCasesCount;
+ public List TestCases { get; internal set; } = new List();
+
public void HandleDiscoveredTests(IEnumerable discoveredTestCases)
{
Console.WriteLine($"[DISCOVERY.PROGRESS]");
Console.WriteLine(WriteTests(discoveredTestCases));
_testCasesCount += discoveredTestCases.Count();
+ if (discoveredTestCases != null) { TestCases.AddRange(discoveredTestCases); }
}
public void HandleDiscoveryComplete(long totalTests, IEnumerable lastChunk, bool isAborted)
@@ -79,6 +110,7 @@ public void HandleDiscoveryComplete(long totalTests, IEnumerable lastC
Console.WriteLine($"[DISCOVERY.COMPLETE] aborted? {isAborted}, tests count: {totalTests}");
Console.WriteLine("Last chunk:");
Console.WriteLine(WriteTests(lastChunk));
+ if (lastChunk != null) { TestCases.AddRange(lastChunk); }
}
public void HandleDiscoveryComplete(DiscoveryCompleteEventArgs discoveryCompleteEventArgs, IEnumerable lastChunk)
@@ -92,6 +124,7 @@ public void HandleDiscoveryComplete(DiscoveryCompleteEventArgs discoveryComplete
Console.WriteLine(WriteSources(discoveryCompleteEventArgs.PartiallyDiscoveredSources));
Console.WriteLine("Not discovered:");
Console.WriteLine(WriteSources(discoveryCompleteEventArgs.NotDiscoveredSources));
+ if (lastChunk != null) { TestCases.AddRange(lastChunk); }
}
public void HandleLogMessage(TestMessageLevel level, string message)
@@ -106,7 +139,7 @@ public void HandleRawMessage(string rawMessage)
private static string WriteTests(IEnumerable testCases)
=> testCases?.Any() == true
- ? "\t" + string.Join("\n\t", testCases.Select(r => r.DisplayName))
+ ? "\t" + string.Join("\n\t", testCases.Select(r => r.Source + " " + r.DisplayName))
: "\t";
private static string WriteSources(IEnumerable sources)
@@ -183,3 +216,28 @@ public int LaunchTestHost(TestProcessStartInfo defaultTestHostStartInfo, Cancell
}
}
}
+
+internal class TestSessionHandler : ITestSessionEventsHandler
+{
+ public TestSessionInfo TestSessionInfo { get; private set; }
+
+ public void HandleLogMessage(TestMessageLevel level, string message)
+ {
+
+ }
+
+ public void HandleRawMessage(string rawMessage)
+ {
+
+ }
+
+ public void HandleStartTestSessionComplete(StartTestSessionCompleteEventArgs eventArgs)
+ {
+ TestSessionInfo = eventArgs.TestSessionInfo;
+ }
+
+ public void HandleStopTestSessionComplete(StopTestSessionCompleteEventArgs eventArgs)
+ {
+
+ }
+}
diff --git a/playground/TestPlatform.Playground/Properties/launchSettings.json b/playground/TestPlatform.Playground/Properties/launchSettings.json
index c688670d9c..7cd5a38d95 100644
--- a/playground/TestPlatform.Playground/Properties/launchSettings.json
+++ b/playground/TestPlatform.Playground/Properties/launchSettings.json
@@ -5,9 +5,9 @@
"environmentVariables": {
"VSTEST_CONNECTION_TIMEOUT": "999",
"VSTEST_DEBUG_NOBP": "1",
- "VSTEST_RUNNER_DEBUG_ATTACHVS": "1",
- "VSTEST_HOST_DEBUG_ATTACHVS": "1",
- "VSTEST_DATACOLLECTOR_DEBUG_ATTACHVS": "1"
+ "VSTEST_RUNNER_DEBUG_ATTACHVS": "0",
+ "VSTEST_HOST_DEBUG_ATTACHVS": "0",
+ "VSTEST_DATACOLLECTOR_DEBUG_ATTACHVS": "0"
}
}
}
diff --git a/scripts/build/TestPlatform.Dependencies.props b/scripts/build/TestPlatform.Dependencies.props
index c1c05c5f9b..0f78ef53b2 100644
--- a/scripts/build/TestPlatform.Dependencies.props
+++ b/scripts/build/TestPlatform.Dependencies.props
@@ -33,9 +33,9 @@
Exact versions are used to avoid Nuget substituting them by closest match, if we make a typo.
These versions need to be "statically" readable because we read this file as xml in our build and tests. -->
- [2.2.9-preview-20220210-07]
- [2.2.8]
- [2.2.7]
+ [2.2.10-preview-20220414-01]
+ [2.2.10]
+ [2.2.8][2.1.0][2.1.0][1.4.0]
@@ -45,9 +45,9 @@
See Invoke-TestAssetsBuild in scripts/build.ps1. Exact versions are used to avoid Nuget substituting them by closest match, if we make a typo.
These versions need to be "statically" readable because we read this file as xml in our build and tests. -->
- [17.2.0-preview-20220131-20]
- [17.1.0]
- [17.0.0]
+ [17.2.0-preview-20220401-08]
+ [17.2.0]
+ [17.1.0][16.6.1][16.11.0][15.9.2]
diff --git a/scripts/build/TestPlatform.targets b/scripts/build/TestPlatform.targets
index df4eb0ec58..737c067d2e 100644
--- a/scripts/build/TestPlatform.targets
+++ b/scripts/build/TestPlatform.targets
@@ -3,7 +3,7 @@
$(MSBuildThisFileDirectory)..\..\false
- $(NoWarn);CA1416
+ $(NoWarn);CA1416;RS0037
diff --git a/src/AttachVS/AttachVs.cs b/src/AttachVS/AttachVs.cs
index acbcfabe52..407c1a5045 100644
--- a/src/AttachVS/AttachVs.cs
+++ b/src/AttachVS/AttachVs.cs
@@ -138,19 +138,12 @@ private static bool AttachVs(Process vs, int pid)
}
}
}
- catch (COMException ex)
+ // Catch the exception if it is COMException coming directly, or coming from methodInvocation, otherwise just let it be.
+ catch (Exception ex) when (ex is COMException || (ex is TargetInvocationException tie && tie.InnerException is COMException))
{
Trace($"ComException: Retrying in 250ms.\n{ex}");
Thread.Sleep(250);
}
- catch (TargetInvocationException ex)
- {
- if (ex.InnerException is not COMException)
- throw;
-
- Trace($"ComException: Retrying in 250ms.\n{ex}");
- Thread.Sleep(250);
- }
}
Marshal.ReleaseComObject(moniker[0]);
diff --git a/src/Microsoft.TestPlatform.Client/TestPlatform.cs b/src/Microsoft.TestPlatform.Client/TestPlatform.cs
index fc3f32da85..d029abb5d9 100644
--- a/src/Microsoft.TestPlatform.Client/TestPlatform.cs
+++ b/src/Microsoft.TestPlatform.Client/TestPlatform.cs
@@ -13,7 +13,6 @@
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 Microsoft.VisualStudio.TestPlatform.CrossPlatEngine;
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
@@ -71,34 +70,27 @@ protected internal TestPlatform(
IFileHelper filehelper,
ITestRuntimeProviderManager testHostProviderManager)
{
- TestEngine = testEngine;
+ _testEngine = testEngine;
_fileHelper = filehelper;
_testHostProviderManager = testHostProviderManager;
}
- ///
- /// Gets or sets the test engine instance.
- ///
- private ITestEngine TestEngine { get; set; }
+ private readonly ITestEngine _testEngine;
///
public IDiscoveryRequest CreateDiscoveryRequest(
IRequestData requestData,
DiscoveryCriteria discoveryCriteria!!,
- TestPlatformOptions options)
+ TestPlatformOptions options,
+ Dictionary sourceToSourceDetailMap)
{
PopulateExtensions(discoveryCriteria.RunSettings, discoveryCriteria.Sources);
// Initialize loggers.
- ITestLoggerManager loggerManager = TestEngine.GetLoggerManager(requestData);
+ ITestLoggerManager loggerManager = _testEngine.GetLoggerManager(requestData);
loggerManager.Initialize(discoveryCriteria.RunSettings);
- ITestRuntimeProvider testHostManager = _testHostProviderManager.GetTestHostManagerByRunConfiguration(discoveryCriteria.RunSettings);
- TestPlatform.ThrowExceptionIfTestHostManagerIsNull(testHostManager, discoveryCriteria.RunSettings);
-
- testHostManager.Initialize(TestSessionMessageLogger.Instance, discoveryCriteria.RunSettings);
-
- IProxyDiscoveryManager discoveryManager = TestEngine.GetDiscoveryManager(requestData, testHostManager, discoveryCriteria);
+ IProxyDiscoveryManager discoveryManager = _testEngine.GetDiscoveryManager(requestData, discoveryCriteria, sourceToSourceDetailMap);
discoveryManager.Initialize(options?.SkipDefaultAdapters ?? false);
return new DiscoveryRequest(requestData, discoveryCriteria, discoveryManager, loggerManager);
@@ -108,32 +100,17 @@ public IDiscoveryRequest CreateDiscoveryRequest(
public ITestRunRequest CreateTestRunRequest(
IRequestData requestData,
TestRunCriteria testRunCriteria!!,
- TestPlatformOptions options)
+ TestPlatformOptions options,
+ Dictionary sourceToSourceDetailMap)
{
IEnumerable sources = GetSources(testRunCriteria);
PopulateExtensions(testRunCriteria.TestRunSettings, sources);
// Initialize loggers.
- ITestLoggerManager loggerManager = TestEngine.GetLoggerManager(requestData);
+ ITestLoggerManager loggerManager = _testEngine.GetLoggerManager(requestData);
loggerManager.Initialize(testRunCriteria.TestRunSettings);
- // TODO: PERF: this will create a testhost manager, and then it will pass that to GetExecutionManager, where it will
- // be used only when we will run in-process. If we don't run in process, we will throw away the manager we just
- // created and let the proxy parallel callbacks to create a new one. This seems to be very easy to move to the GetExecutionManager,
- // and safe as well, so we create the manager only once.
- // TODO: Of course TestEngine.GetExecutionManager is public api...
- ITestRuntimeProvider testHostManager = _testHostProviderManager.GetTestHostManagerByRunConfiguration(testRunCriteria.TestRunSettings);
- TestPlatform.ThrowExceptionIfTestHostManagerIsNull(testHostManager, testRunCriteria.TestRunSettings);
-
- testHostManager.Initialize(TestSessionMessageLogger.Instance, testRunCriteria.TestRunSettings);
-
- // NOTE: The custom launcher should not be set when we have test session info available.
- if (testRunCriteria.TestHostLauncher != null)
- {
- testHostManager.SetCustomLauncher(testRunCriteria.TestHostLauncher);
- }
-
- IProxyExecutionManager executionManager = TestEngine.GetExecutionManager(requestData, testHostManager, testRunCriteria);
+ IProxyExecutionManager executionManager = _testEngine.GetExecutionManager(requestData, testRunCriteria, sourceToSourceDetailMap);
executionManager.Initialize(options?.SkipDefaultAdapters ?? false);
return new TestRunRequest(requestData, testRunCriteria, executionManager, loggerManager);
@@ -143,7 +120,8 @@ public ITestRunRequest CreateTestRunRequest(
public bool StartTestSession(
IRequestData requestData,
StartTestSessionCriteria testSessionCriteria!!,
- ITestSessionEventsHandler eventsHandler)
+ ITestSessionEventsHandler eventsHandler,
+ Dictionary sourceToSourceDetailMap)
{
RunConfiguration runConfiguration = XmlRunSettingsUtilities.GetRunConfigurationNode(testSessionCriteria.RunSettings);
TestAdapterLoadingStrategy strategy = runConfiguration.TestAdapterLoadingStrategy;
@@ -155,7 +133,7 @@ public bool StartTestSession(
return false;
}
- IProxyTestSessionManager testSessionManager = TestEngine.GetTestSessionManager(requestData, testSessionCriteria);
+ IProxyTestSessionManager testSessionManager = _testEngine.GetTestSessionManager(requestData, testSessionCriteria, sourceToSourceDetailMap);
if (testSessionManager == null)
{
// The test session manager is null because the combination of runsettings and
@@ -197,13 +175,13 @@ public void UpdateExtensions(
IEnumerable pathToAdditionalExtensions,
bool skipExtensionFilters)
{
- TestEngine.GetExtensionManager().UseAdditionalExtensions(pathToAdditionalExtensions, skipExtensionFilters);
+ _testEngine.GetExtensionManager().UseAdditionalExtensions(pathToAdditionalExtensions, skipExtensionFilters);
}
///
public void ClearExtensions()
{
- TestEngine.GetExtensionManager().ClearExtensions();
+ _testEngine.GetExtensionManager().ClearExtensions();
}
private static void ThrowExceptionIfTestHostManagerIsNull(
diff --git a/src/Microsoft.TestPlatform.Common/Hosting/ITestRuntimeProviderManager.cs b/src/Microsoft.TestPlatform.Common/Hosting/ITestRuntimeProviderManager.cs
index 781e002fad..d1073bc2ab 100644
--- a/src/Microsoft.TestPlatform.Common/Hosting/ITestRuntimeProviderManager.cs
+++ b/src/Microsoft.TestPlatform.Common/Hosting/ITestRuntimeProviderManager.cs
@@ -1,12 +1,14 @@
// 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.Collections.Generic;
+
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Host;
namespace Microsoft.VisualStudio.TestPlatform.Common.Hosting;
internal interface ITestRuntimeProviderManager
{
- ITestRuntimeProvider GetTestHostManagerByRunConfiguration(string runConfiguration);
+ ITestRuntimeProvider GetTestHostManagerByRunConfiguration(string runConfiguration, List sources);
ITestRuntimeProvider GetTestHostManagerByUri(string hostUri);
}
diff --git a/src/Microsoft.TestPlatform.Common/Hosting/TestRunTimeProviderManager.cs b/src/Microsoft.TestPlatform.Common/Hosting/TestRunTimeProviderManager.cs
index 5730b4de84..d0cbf55fec 100644
--- a/src/Microsoft.TestPlatform.Common/Hosting/TestRunTimeProviderManager.cs
+++ b/src/Microsoft.TestPlatform.Common/Hosting/TestRunTimeProviderManager.cs
@@ -2,6 +2,7 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
+using System.Collections.Generic;
using Microsoft.VisualStudio.TestPlatform.Common.Logging;
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Host;
@@ -44,7 +45,7 @@ public ITestRuntimeProvider GetTestHostManagerByUri(string hostUri)
return host?.Value;
}
- public virtual ITestRuntimeProvider GetTestHostManagerByRunConfiguration(string runConfiguration)
+ public virtual ITestRuntimeProvider GetTestHostManagerByRunConfiguration(string runConfiguration, List _)
{
foreach (var testExtension in _testHostExtensionManager.TestExtensions)
{
diff --git a/src/Microsoft.TestPlatform.Common/Interfaces/Engine/ClientProtocol/IParallelProxyDiscoveryManager.cs b/src/Microsoft.TestPlatform.Common/Interfaces/Engine/ClientProtocol/IParallelProxyDiscoveryManager.cs
index 96011d53bc..9e32610fd4 100644
--- a/src/Microsoft.TestPlatform.Common/Interfaces/Engine/ClientProtocol/IParallelProxyDiscoveryManager.cs
+++ b/src/Microsoft.TestPlatform.Common/Interfaces/Engine/ClientProtocol/IParallelProxyDiscoveryManager.cs
@@ -10,7 +10,7 @@ namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine;
///
/// Interface defining the parallel discovery manager
///
-public interface IParallelProxyDiscoveryManager : IParallelOperationManager, IProxyDiscoveryManager
+public interface IParallelProxyDiscoveryManager : IProxyDiscoveryManager
{
///
/// Indicates if user requested an abortion
diff --git a/src/Microsoft.TestPlatform.Common/Interfaces/Engine/ClientProtocol/IParallelProxyExecutionManager.cs b/src/Microsoft.TestPlatform.Common/Interfaces/Engine/ClientProtocol/IParallelProxyExecutionManager.cs
index 80c1675c8f..8b18e34d47 100644
--- a/src/Microsoft.TestPlatform.Common/Interfaces/Engine/ClientProtocol/IParallelProxyExecutionManager.cs
+++ b/src/Microsoft.TestPlatform.Common/Interfaces/Engine/ClientProtocol/IParallelProxyExecutionManager.cs
@@ -12,7 +12,7 @@ namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine;
///
/// Interface defining the parallel execution manager
///
-public interface IParallelProxyExecutionManager : IParallelOperationManager, IProxyExecutionManager
+public interface IParallelProxyExecutionManager : IProxyExecutionManager
{
///
/// Handles Partial Run Complete event coming from a specific concurrent proxy execution manager
diff --git a/src/Microsoft.TestPlatform.Common/Interfaces/Engine/ClientProtocol/ITestEngine.cs b/src/Microsoft.TestPlatform.Common/Interfaces/Engine/ClientProtocol/ITestEngine.cs
index 8fea354758..369049c605 100644
--- a/src/Microsoft.TestPlatform.Common/Interfaces/Engine/ClientProtocol/ITestEngine.cs
+++ b/src/Microsoft.TestPlatform.Common/Interfaces/Engine/ClientProtocol/ITestEngine.cs
@@ -1,9 +1,9 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
-using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client;
+using System.Collections.Generic;
-using Microsoft.VisualStudio.TestPlatform.ObjectModel.Host;
+using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client;
#nullable disable
@@ -28,8 +28,8 @@ public interface ITestEngine
/// An IProxyDiscoveryManager object that can do discovery.
IProxyDiscoveryManager GetDiscoveryManager(
IRequestData requestData,
- ITestRuntimeProvider testHostManager,
- DiscoveryCriteria discoveryCriteria);
+ DiscoveryCriteria discoveryCriteria,
+ IDictionary sourceToSourceDetailMap);
///
/// Fetches the ExecutionManager for this engine. This manager would provide all
@@ -45,8 +45,8 @@ IProxyDiscoveryManager GetDiscoveryManager(
/// An IProxyExecutionManager object that can do execution.
IProxyExecutionManager GetExecutionManager(
IRequestData requestData,
- ITestRuntimeProvider testHostManager,
- TestRunCriteria testRunCriteria);
+ TestRunCriteria testRunCriteria,
+ IDictionary sourceToSourceDetailMap);
///
/// Fetches the TestSessionManager for this engine. This manager would provide all
@@ -63,7 +63,8 @@ IProxyExecutionManager GetExecutionManager(
/// An IProxyTestSessionManager object that can manage test sessions.
IProxyTestSessionManager GetTestSessionManager(
IRequestData requestData,
- StartTestSessionCriteria testSessionCriteria);
+ StartTestSessionCriteria testSessionCriteria,
+ IDictionary sourceToSourceDetailMap);
///
/// Fetches the extension manager for this engine. This manager would provide extensibility
diff --git a/src/Microsoft.TestPlatform.Common/PublicAPI/PublicAPI.Shipped.txt b/src/Microsoft.TestPlatform.Common/PublicAPI/PublicAPI.Shipped.txt
index c5d6770657..b9d09e94a1 100644
--- a/src/Microsoft.TestPlatform.Common/PublicAPI/PublicAPI.Shipped.txt
+++ b/src/Microsoft.TestPlatform.Common/PublicAPI/PublicAPI.Shipped.txt
@@ -174,8 +174,6 @@ Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine.ClientProtocol.TestExecut
Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine.ClientProtocol.TestExecutionContext.TestExecutionContext(long frequencyOfRunStatsChangeEvent, System.TimeSpan runStatsChangeEventTimeout, bool inIsolation, bool keepAlive, bool isDataCollectionEnabled, bool areTestCaseLevelEventsRequired, bool hasTestRun, bool isDebug, string testCaseFilter, Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.FilterOptions filterOptions) -> void
Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine.ClientProtocol.TestExecutionContext.TestRunConfiguration.get -> Microsoft.VisualStudio.TestPlatform.ObjectModel.RunConfiguration
Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine.ClientProtocol.TestExecutionContext.TestRunConfiguration.set -> void
-Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine.IParallelOperationManager
-Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine.IParallelOperationManager.UpdateParallelLevel(int parallelLevel) -> void
Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine.IParallelProxyDiscoveryManager
Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine.IParallelProxyDiscoveryManager.HandlePartialDiscoveryComplete(Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine.IProxyDiscoveryManager proxyDiscoveryManager, long totalTests, System.Collections.Generic.IEnumerable lastChunk, bool isAborted) -> bool
Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine.IParallelProxyExecutionManager
@@ -200,11 +198,8 @@ Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine.ITestCaseEventsHandler.Se
Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine.ITestCaseEventsHandler.SendTestCaseStart(Microsoft.VisualStudio.TestPlatform.ObjectModel.TestCase testCase) -> void
Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine.ITestCaseEventsHandler.SendTestResult(Microsoft.VisualStudio.TestPlatform.ObjectModel.TestResult result) -> void
Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine.ITestEngine
-Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine.ITestEngine.GetDiscoveryManager(Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.IRequestData requestData, Microsoft.VisualStudio.TestPlatform.ObjectModel.Host.ITestRuntimeProvider testHostManager, Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.DiscoveryCriteria discoveryCriteria) -> Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine.IProxyDiscoveryManager
-Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine.ITestEngine.GetExecutionManager(Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.IRequestData requestData, Microsoft.VisualStudio.TestPlatform.ObjectModel.Host.ITestRuntimeProvider testHostManager, Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.TestRunCriteria testRunCriteria) -> Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine.IProxyExecutionManager
Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine.ITestEngine.GetExtensionManager() -> Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine.ITestExtensionManager
Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine.ITestEngine.GetLoggerManager(Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.IRequestData requestData) -> Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine.ITestLoggerManager
-Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine.ITestEngine.GetTestSessionManager(Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.IRequestData requestData, Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.StartTestSessionCriteria testSessionCriteria) -> Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine.IProxyTestSessionManager
Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine.ITestExtensionManager
Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine.ITestExtensionManager.ClearExtensions() -> void
Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine.ITestExtensionManager.UseAdditionalExtensions(System.Collections.Generic.IEnumerable pathToAdditionalExtensions, bool skipExtensionFilters) -> void
@@ -251,7 +246,7 @@ static Microsoft.VisualStudio.TestPlatform.Common.Utilities.RunSettingsUtilities
virtual Microsoft.VisualStudio.TestPlatform.Common.ExtensionFramework.TestPluginCache.GetFilteredExtensions(System.Collections.Generic.List extensions, string endsWithPattern) -> System.Collections.Generic.IEnumerable
virtual Microsoft.VisualStudio.TestPlatform.Common.ExtensionFramework.Utilities.TestPluginInformation.IdentifierData.get -> string
virtual Microsoft.VisualStudio.TestPlatform.Common.ExtensionFramework.Utilities.TestPluginInformation.Metadata.get -> System.Collections.Generic.ICollection
/// Proxy discovery manager instance.
- private void DiscoverTestsOnConcurrentManager(IProxyDiscoveryManager proxyDiscoveryManager)
+ private void DiscoverTestsOnConcurrentManager(IProxyDiscoveryManager proxyDiscoveryManager, ITestDiscoveryEventsHandler2 eventHandler, DiscoveryCriteria discoveryCriteria)
{
- Debug.Assert(_actualDiscoveryCriteria is not null, "Discovery criteria is null, DiscoverTests should have been called before reaching this point.");
- // Peek to see if we have sources to trigger a discovery
- if (TryFetchNextSource(_sourceEnumerator, out string nextSource))
- {
- EqtTrace.Verbose("ProxyParallelDiscoveryManager.DiscoverTestsOnConcurrentManager: Triggering test discovery for next source: {0}", nextSource);
+ // Kick off another discovery task for the next source
+ Task.Run(() =>
+ {
+ EqtTrace.Verbose("ParallelProxyDiscoveryManager: Discovery started.");
- // Kick off another discovery task for the next source
- var discoveryCriteria = new DiscoveryCriteria(new[] { nextSource }, _actualDiscoveryCriteria.FrequencyOfDiscoveredTestsEvent, _actualDiscoveryCriteria.DiscoveredTestEventTimeout, _actualDiscoveryCriteria.RunSettings);
- discoveryCriteria.TestCaseFilter = _actualDiscoveryCriteria.TestCaseFilter;
- Task.Run(() =>
+ proxyDiscoveryManager.Initialize(_skipDefaultAdapters);
+ proxyDiscoveryManager.DiscoverTests(discoveryCriteria, eventHandler);
+ })
+ .ContinueWith(t =>
{
- EqtTrace.Verbose("ParallelProxyDiscoveryManager.DiscoverTestsOnConcurrentManager: Discovery started.");
-
- proxyDiscoveryManager.DiscoverTests(discoveryCriteria, GetHandlerForGivenManager(proxyDiscoveryManager));
- })
- .ContinueWith(t =>
- {
- // Just in case, the actual discovery couldn't start for an instance. Ensure that
- // we call discovery complete since we have already fetched a source. Otherwise
- // discovery will not terminate
- EqtTrace.Error("ParallelProxyDiscoveryManager.DiscoverTestsOnConcurrentManager: Failed to trigger discovery. Exception: " + t.Exception);
-
- var handler = GetHandlerForGivenManager(proxyDiscoveryManager);
- var testMessagePayload = new TestMessagePayload { MessageLevel = TestMessageLevel.Error, Message = t.Exception.ToString() };
- handler.HandleRawMessage(_dataSerializer.SerializePayload(MessageType.TestMessage, testMessagePayload));
- handler.HandleLogMessage(TestMessageLevel.Error, t.Exception.ToString());
-
- // Send discovery complete. Similar logic is also used in ProxyDiscoveryManager.DiscoverTests.
- // Differences:
- // Total tests must be zero here since parallel discovery events handler adds the count
- // Keep `lastChunk` as null since we don't want a message back to the IDE (discovery didn't even begin)
- // Set `isAborted` as true since we want this instance of discovery manager to be replaced
- var discoveryCompleteEventsArgs = new DiscoveryCompleteEventArgs(-1, true);
- handler.HandleDiscoveryComplete(discoveryCompleteEventsArgs, null);
- },
- TaskContinuationOptions.OnlyOnFaulted);
- }
+ // Just in case, the actual discovery couldn't start for an instance. Ensure that
+ // we call discovery complete since we have already fetched a source. Otherwise
+ // discovery will not terminate
+ EqtTrace.Error("ParallelProxyDiscoveryManager: Failed to trigger discovery. Exception: " + t.Exception);
+
+ var handler = eventHandler;
+ var testMessagePayload = new TestMessagePayload { MessageLevel = TestMessageLevel.Error, Message = t.Exception.ToString() };
+ handler.HandleRawMessage(_dataSerializer.SerializePayload(MessageType.TestMessage, testMessagePayload));
+ handler.HandleLogMessage(TestMessageLevel.Error, t.Exception.ToString());
+
+ // Send discovery complete. Similar logic is also used in ProxyDiscoveryManager.DiscoverTests.
+ // Differences:
+ // Total tests must be zero here since parallel discovery events handler adds the count
+ // Keep `lastChunk` as null since we don't want a message back to the IDE (discovery didn't even begin)
+ // Set `isAborted` as true since we want this instance of discovery manager to be replaced
+ // TODO: the comment above mentions 0 tests but sends -1. Make sense of this.
+ var discoveryCompleteEventsArgs = new DiscoveryCompleteEventArgs(-1, true);
+ handler.HandleDiscoveryComplete(discoveryCompleteEventsArgs, null);
+ },
+ TaskContinuationOptions.OnlyOnFaulted);
EqtTrace.Verbose("ProxyParallelDiscoveryManager.DiscoverTestsOnConcurrentManager: No sources available for discovery.");
}
diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Client/Parallel/ParallelProxyExecutionManager.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/Client/Parallel/ParallelProxyExecutionManager.cs
index 073ba8b21a..682f8ace01 100644
--- a/src/Microsoft.TestPlatform.CrossPlatEngine/Client/Parallel/ParallelProxyExecutionManager.cs
+++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Client/Parallel/ParallelProxyExecutionManager.cs
@@ -2,7 +2,6 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
-using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
@@ -27,9 +26,11 @@ namespace Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Client.Parallel;
///
/// ParallelProxyExecutionManager that manages parallel execution
///
-internal class ParallelProxyExecutionManager : ParallelOperationManager, IParallelProxyExecutionManager
+internal class ParallelProxyExecutionManager : IParallelProxyExecutionManager
{
private readonly IDataSerializer _dataSerializer;
+ private readonly ParallelOperationManager _parallelOperationManager;
+ private readonly Dictionary _sourceToTestHostProviderMap;
#region TestRunSpecificData
@@ -38,17 +39,7 @@ internal class ParallelProxyExecutionManager : ParallelOperationManager _sourceEnumerator;
-
- private IEnumerator _testCaseListEnumerator;
-
- private bool _hasSpecificTestsRun;
-
- private ITestRunEventsHandler _currentRunEventsHandler;
+ private int _availableWorkloads = -1;
private ParallelRunDataAggregator _currentRunDataAggregator;
@@ -69,102 +60,73 @@ internal class ParallelProxyExecutionManager : ParallelOperationManager actualProxyManagerCreator, int parallelLevel)
- : this(requestData, actualProxyManagerCreator, JsonDataSerializer.Instance, parallelLevel, true)
+ public ParallelProxyExecutionManager(
+ IRequestData requestData,
+ Func actualProxyManagerCreator,
+ int parallelLevel,
+ List testHostProviders)
+ : this(requestData, actualProxyManagerCreator, JsonDataSerializer.Instance, parallelLevel, testHostProviders)
{
}
- public ParallelProxyExecutionManager(IRequestData requestData, Func actualProxyManagerCreator, int parallelLevel, bool sharedHosts)
- : this(requestData, actualProxyManagerCreator, JsonDataSerializer.Instance, parallelLevel, sharedHosts)
- {
- }
-
- internal ParallelProxyExecutionManager(IRequestData requestData, Func actualProxyManagerCreator, IDataSerializer dataSerializer, int parallelLevel, bool sharedHosts)
- : base(actualProxyManagerCreator, parallelLevel, sharedHosts)
+ internal ParallelProxyExecutionManager(
+ IRequestData requestData,
+ Func actualProxyManagerCreator,
+ IDataSerializer dataSerializer,
+ int parallelLevel,
+ List testHostProviders)
{
_requestData = requestData;
_dataSerializer = dataSerializer;
+ _parallelOperationManager = new(actualProxyManagerCreator, parallelLevel);
+ _sourceToTestHostProviderMap = testHostProviders
+ .SelectMany(provider => provider.SourceDetails.Select(s => new KeyValuePair(s.Source, provider)))
+ .ToDictionary(pair => pair.Key, pair => pair.Value);
}
- #region IProxyExecutionManager
-
public void Initialize(bool skipDefaultAdapters)
{
_skipDefaultAdapters = skipDefaultAdapters;
- DoActionOnAllManagers((proxyManager) => proxyManager.Initialize(skipDefaultAdapters), doActionsInParallel: true);
- IsInitialized = true;
}
public int StartTestRun(TestRunCriteria testRunCriteria, ITestRunEventsHandler eventHandler)
{
- _hasSpecificTestsRun = testRunCriteria.HasSpecificTests;
- _actualTestRunCriteria = testRunCriteria;
+ var workloads = SplitToWorkloads(testRunCriteria, _sourceToTestHostProviderMap);
+ _availableWorkloads = workloads.Count;
- if (_hasSpecificTestsRun)
- {
- var testCasesBySource = new Dictionary>();
- foreach (var test in testRunCriteria.Tests)
- {
- if (!testCasesBySource.ContainsKey(test.Source))
- {
- testCasesBySource.Add(test.Source, new List());
- }
+ EqtTrace.Verbose("ParallelProxyExecutionManager: Start execution. Total sources: " + _availableWorkloads);
- testCasesBySource[test.Source].Add(test);
- }
+ // Reset the run complete data
+ _runCompletedClients = 0;
- // Do not use "Dictionary.ValueCollection.Enumerator" - it becomes nondeterministic once we go out of scope of this method
- // Use "ToArray" to copy ValueColleciton to a simple array and use it's enumerator
- // Set the enumerator for parallel yielding of testCases
- // Whenever a concurrent executor becomes free, it picks up the next set of testCases using this enumerator
- var testCaseLists = testCasesBySource.Values.ToArray();
- _testCaseListEnumerator = testCaseLists.GetEnumerator();
- _availableTestSources = testCaseLists.Length;
- }
- else
- {
- // Set the enumerator for parallel yielding of sources
- // Whenever a concurrent executor becomes free, it picks up the next source using this enumerator
- _sourceEnumerator = testRunCriteria.Sources.GetEnumerator();
- _availableTestSources = testRunCriteria.Sources.Count();
- }
+ // One data aggregator per parallel run
+ _currentRunDataAggregator = new ParallelRunDataAggregator(testRunCriteria.TestRunSettings);
- EqtTrace.Verbose("ParallelProxyExecutionManager: Start execution. Total sources: " + _availableTestSources);
+ _parallelOperationManager.StartWork(workloads, eventHandler, GetParallelEventHandler, StartTestRunOnConcurrentManager);
- return StartTestRunPrivate(eventHandler);
+ // Why 1? Because this is supposed to be a processId, and that is just the default that was chosen by someone before me,
+ // and maybe is checked somewhere, but I don't see it checked in our codebase.
+ return 1;
}
public void Abort(ITestRunEventsHandler runEventsHandler)
{
// Test platform initiated abort.
_abortRequested = true;
- DoActionOnAllManagers((proxyManager) => proxyManager.Abort(runEventsHandler), doActionsInParallel: true);
+ _parallelOperationManager.DoActionOnAllManagers((proxyManager) => proxyManager.Abort(runEventsHandler), doActionsInParallel: true);
}
public void Cancel(ITestRunEventsHandler runEventsHandler)
{
- DoActionOnAllManagers((proxyManager) => proxyManager.Cancel(runEventsHandler), doActionsInParallel: true);
+ _parallelOperationManager.DoActionOnAllManagers((proxyManager) => proxyManager.Cancel(runEventsHandler), doActionsInParallel: true);
}
public void Close()
{
- DoActionOnAllManagers(proxyManager => proxyManager.Close(), doActionsInParallel: true);
+ _parallelOperationManager.DoActionOnAllManagers(proxyManager => proxyManager.Close(), doActionsInParallel: true);
}
- #endregion
-
- #region IParallelProxyExecutionManager methods
-
- ///
- /// Handles Partial Run Complete event coming from a specific concurrent proxy execution manager
- /// Each concurrent proxy execution manager will signal the parallel execution manager when its complete
- ///
- /// Concurrent Execution manager that completed the run
- /// RunCompleteArgs for the concurrent run
- /// LastChunk testresults for the concurrent run
- /// RunAttachments for the concurrent run
- /// ExecutorURIs of the adapters involved in executing the tests
- /// True if parallel run is complete
+ ///
public bool HandlePartialRunComplete(
IProxyExecutionManager proxyExecutionManager,
TestRunCompleteEventArgs testRunCompleteArgs,
@@ -173,6 +135,7 @@ public bool HandlePartialRunComplete(
ICollection executorUris)
{
var allRunsCompleted = false;
+ // TODO: Interlocked.Increment _runCompletedClients, and the condition on the bottom probably does not need to be under lock??
lock (_executionStatusLockObject)
{
// Each concurrent Executor calls this method
@@ -181,69 +144,137 @@ public bool HandlePartialRunComplete(
allRunsCompleted = testRunCompleteArgs.IsCanceled || _abortRequested
? _runCompletedClients == _runStartedClients
- : _runCompletedClients == _availableTestSources;
+ : _runCompletedClients == _availableWorkloads;
EqtTrace.Verbose("ParallelProxyExecutionManager: HandlePartialRunComplete: Total completed clients = {0}, Run complete = {1}, Run canceled: {2}.", _runCompletedClients, allRunsCompleted, testRunCompleteArgs.IsCanceled);
}
- // verify that all executors are done with the execution and there are no more sources/testcases to execute
if (allRunsCompleted)
{
- // Reset enumerators
- _sourceEnumerator = null;
- _testCaseListEnumerator = null;
-
- _currentRunDataAggregator = null;
- _currentRunEventsHandler = null;
-
- // Dispose concurrent executors
- // Do not do the cleanup task in the current thread as we will unnecessarily add to execution time
- UpdateParallelLevel(0);
-
+ _parallelOperationManager.StopAllManagers();
return true;
}
-
- EqtTrace.Verbose("ParallelProxyExecutionManager: HandlePartialRunComplete: Replace execution manager. Shared: {0}, Aborted: {1}.", SharedHosts, testRunCompleteArgs.IsAborted);
-
- RemoveManager(proxyExecutionManager);
- proxyExecutionManager = CreateNewConcurrentManager();
- var parallelEventsHandler = GetEventsHandler(proxyExecutionManager);
- AddManager(proxyExecutionManager, parallelEventsHandler);
-
// If cancel is triggered for any one run or abort is requested by test platform, there is no reason to fetch next source
- // and queue another test run
+ // and queue another test run.
if (!testRunCompleteArgs.IsCanceled && !_abortRequested)
{
- StartTestRunOnConcurrentManager(proxyExecutionManager);
+ // Do NOT return true here, there should be only one place where this method returns true,
+ // and cancellation or success or any other other combination or timing should result in only one true.
+ // This is largely achieved by returning true above when "allRunsCompleted" is true. That variable is true
+ // when we cancel all sources or when we complete all sources.
+ //
+ // But we can also start a source, and cancel right after, which will remove all managers, and RunNextWork returns
+ // false, because we had no more work to do. If we check that result here and return true, then the whole logic is
+ // broken and we end up calling RunComplete handlers twice and writing logger output to screen twice. So don't do it.
+ // var hadMoreWork = _parallelOperationManager.RunNextWork(proxyExecutionManager);
+ // if (!hadMoreWork)
+ // {
+ // return true;
+ // }
+ var _ = _parallelOperationManager.RunNextWork(proxyExecutionManager);
}
return false;
}
- #endregion
-
- private int StartTestRunPrivate(ITestRunEventsHandler runEventsHandler)
+ ///
+ /// Split the incoming work into smaller workloads that we can run on different testhosts.
+ /// Each workload is associated with a type of provider that can run it.
+ ///
+ ///
+ ///
+ ///
+ private List> SplitToWorkloads(TestRunCriteria testRunCriteria, Dictionary sourceToTestHostProviderMap)
{
- _currentRunEventsHandler = runEventsHandler;
+ // We split the work to workloads that will run on each testhost, and add all of them
+ // to a bag of work that needs to be processed. (The workloads are just
+ // a single source, or all test cases for a given source.)
+ //
+ // For every workload we associated a given type of testhost that can run the work.
+ // This is important when we have shared testhosts. A shared testhost can re-use the same process
+ // to run more than one workload, as long as the provider is the same.
+ //
+ // We then start as many instances of testhost as we are allowed by parallel level,
+ // and we start sending them work. Once any testhost is done processing a given workload,
+ // we will get notified with the completed event for the work we are doing. For example for StartTestRun
+ // we will get TestExecutionCompleted, and we will call HandlePartialTestExecutionComplete.
+ // (The "partial" here refers to possibly having more work in the work bag. It does not mean that
+ // there was an error in the testhost and we only did part of execution.).
+ //
+ // At that point we know that at least one testhost is not busy doing work anymore. It either
+ // processed the workload and waits for another one, or it crashed and we should move to
+ // another source.
+ //
+ // In the "partial" step we check if we have more workloads, and if the currently running testhost
+ // is shared we try to find a workload that is appropriate for it. If we don't find any work that the
+ // running testhost can do. Or if the testhost already exited (possibly because of crash), we start another one
+ // and give it the next workload.
+ List> workloads = new();
+ if (testRunCriteria.HasSpecificTests)
+ {
+ // We split test cases to their respective sources, and associate them with additional info about on
+ // which type of provider they can run so we can later select the correct workload for the provider
+ // if we already have a shared provider running, that can take more sources.
+ var testCasesPerSource = testRunCriteria.Tests.GroupBy(t => t.Source);
+ foreach (var group in testCasesPerSource)
+ {
+ var testHostProviderInfo = sourceToTestHostProviderMap[group.Key];
+ var runsettings = testHostProviderInfo.RunSettings;
+ // ToList because it is easier to see what is going on when debugging.
+ var testCases = group.ToList();
+ var updatedCriteria = CreateTestRunCriteriaFromTestCasesAndSettings(testCases, testRunCriteria, runsettings);
+ var workload = new ProviderSpecificWorkload(updatedCriteria, testHostProviderInfo);
+ workloads.Add(workload);
+ }
- // Reset the run complete data
- _runCompletedClients = 0;
+ }
+ else
+ {
+ // We associate every source with additional info about on which type of provider it can run so we can later
+ // select the correct workload for the provider if we already have a provider running, and it is shared.
+ foreach (var source in testRunCriteria.Sources)
+ {
+ var testHostProviderInfo = sourceToTestHostProviderMap[source];
+ var runsettings = testHostProviderInfo.RunSettings;
+ var updatedCriteria = CreateTestRunCriteriaFromSourceAndSettings(new[] { source }, testRunCriteria, runsettings);
+ var workload = new ProviderSpecificWorkload(updatedCriteria, testHostProviderInfo);
+ workloads.Add(workload);
+ }
+ }
- // One data aggregator per parallel run
- _currentRunDataAggregator = new ParallelRunDataAggregator(_actualTestRunCriteria.TestRunSettings);
+ return workloads;
- foreach (var concurrentManager in GetConcurrentManagerInstances())
+ TestRunCriteria CreateTestRunCriteriaFromTestCasesAndSettings(IEnumerable testCases, TestRunCriteria criteria, string runsettingsXml)
{
- var parallelEventsHandler = GetEventsHandler(concurrentManager);
- UpdateHandlerForManager(concurrentManager, parallelEventsHandler);
- StartTestRunOnConcurrentManager(concurrentManager);
+ return new TestRunCriteria(
+ testCases,
+ testRunCriteria.FrequencyOfRunStatsChangeEvent,
+ testRunCriteria.KeepAlive,
+ runsettingsXml,
+ testRunCriteria.RunStatsChangeEventTimeout,
+ testRunCriteria.TestHostLauncher,
+ testRunCriteria.TestSessionInfo,
+ testRunCriteria.DebugEnabledForTestSession);
}
- return 1;
+ TestRunCriteria CreateTestRunCriteriaFromSourceAndSettings(IEnumerable sources, TestRunCriteria criteria, string runsettingsXml)
+ {
+ return new TestRunCriteria(
+ sources,
+ testRunCriteria.FrequencyOfRunStatsChangeEvent,
+ testRunCriteria.KeepAlive,
+ runsettingsXml,
+ testRunCriteria.RunStatsChangeEventTimeout,
+ testRunCriteria.TestHostLauncher,
+ testRunCriteria.TestCaseFilter,
+ testRunCriteria.FilterOptions,
+ testRunCriteria.TestSessionInfo,
+ testRunCriteria.DebugEnabledForTestSession);
+ }
}
- private ParallelRunEventsHandler GetEventsHandler(IProxyExecutionManager concurrentManager)
+ private ParallelRunEventsHandler GetParallelEventHandler(ITestRunEventsHandler eventHandler, IProxyExecutionManager concurrentManager)
{
if (concurrentManager is ProxyExecutionManagerWithDataCollection)
{
@@ -253,7 +284,7 @@ private ParallelRunEventsHandler GetEventsHandler(IProxyExecutionManager concurr
return new ParallelDataCollectionEventsHandler(
_requestData,
concurrentManagerWithDataCollection,
- _currentRunEventsHandler,
+ eventHandler,
this,
_currentRunDataAggregator,
attachmentsProcessingManager,
@@ -263,7 +294,7 @@ private ParallelRunEventsHandler GetEventsHandler(IProxyExecutionManager concurr
return new ParallelRunEventsHandler(
_requestData,
concurrentManager,
- _currentRunEventsHandler,
+ eventHandler,
this,
_currentRunDataAggregator);
}
@@ -274,26 +305,8 @@ private ParallelRunEventsHandler GetEventsHandler(IProxyExecutionManager concurr
///
/// Proxy execution manager instance.
/// True, if execution triggered
- private void StartTestRunOnConcurrentManager(IProxyExecutionManager proxyExecutionManager)
+ private void StartTestRunOnConcurrentManager(IProxyExecutionManager proxyExecutionManager, ITestRunEventsHandler eventHandler, TestRunCriteria testRunCriteria)
{
- TestRunCriteria testRunCriteria = null;
- if (!_hasSpecificTestsRun)
- {
- if (TryFetchNextSource(_sourceEnumerator, out string nextSource))
- {
- EqtTrace.Info("ProxyParallelExecutionManager: Triggering test run for next source: {0}", nextSource);
- testRunCriteria = new TestRunCriteria(new[] { nextSource }, _actualTestRunCriteria);
- }
- }
- else
- {
- if (TryFetchNextSource(_testCaseListEnumerator, out List nextSetOfTests))
- {
- EqtTrace.Info("ProxyParallelExecutionManager: Triggering test run for next source: {0}", nextSetOfTests?.FirstOrDefault()?.Source);
- testRunCriteria = new TestRunCriteria(nextSetOfTests, _actualTestRunCriteria);
- }
- }
-
if (testRunCriteria != null)
{
if (!proxyExecutionManager.IsInitialized)
@@ -306,7 +319,7 @@ private void StartTestRunOnConcurrentManager(IProxyExecutionManager proxyExecuti
Interlocked.Increment(ref _runStartedClients);
EqtTrace.Verbose("ParallelProxyExecutionManager: Execution started. Started clients: " + _runStartedClients);
- proxyExecutionManager.StartTestRun(testRunCriteria, GetHandlerForGivenManager(proxyExecutionManager));
+ proxyExecutionManager.StartTestRun(testRunCriteria, eventHandler);
})
.ContinueWith(t =>
{
@@ -315,7 +328,7 @@ private void StartTestRunOnConcurrentManager(IProxyExecutionManager proxyExecuti
// execution will not terminate
EqtTrace.Error("ParallelProxyExecutionManager: Failed to trigger execution. Exception: " + t.Exception);
- var handler = GetHandlerForGivenManager(proxyExecutionManager);
+ var handler = eventHandler;
var testMessagePayload = new TestMessagePayload { MessageLevel = TestMessageLevel.Error, Message = t.Exception.ToString() };
handler.HandleRawMessage(_dataSerializer.SerializePayload(MessageType.TestMessage, testMessagePayload));
handler.HandleLogMessage(TestMessageLevel.Error, t.Exception.ToString());
@@ -334,3 +347,21 @@ private void StartTestRunOnConcurrentManager(IProxyExecutionManager proxyExecuti
EqtTrace.Verbose("ProxyParallelExecutionManager: No sources available for execution.");
}
}
+
+///
+/// A workload with a specification of a provider that can run that workload. The workload is a list of sources,
+/// or a list of testcases. Provider is a testhost manager, that is capable of running this workload, so
+/// we end up running .NET sources on .NET testhost, and .NET Framework sources on .NET Framework testhost.
+///
+internal class ProviderSpecificWorkload
+{
+ public T Work { get; }
+
+ public TestRuntimeProviderInfo Provider { get; protected set; }
+
+ public ProviderSpecificWorkload(T work, TestRuntimeProviderInfo provider)
+ {
+ Provider = provider;
+ Work = work;
+ }
+}
diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Client/ProxyDiscoveryManager.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/Client/ProxyDiscoveryManager.cs
index 9fe0bfae09..b361cc9934 100644
--- a/src/Microsoft.TestPlatform.CrossPlatEngine/Client/ProxyDiscoveryManager.cs
+++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Client/ProxyDiscoveryManager.cs
@@ -132,6 +132,7 @@ public void DiscoverTests(DiscoveryCriteria discoveryCriteria, ITestDiscoveryEve
if (_proxyOperationManager == null)
{
+ // Passing only first because that is how the testhost pool is keyed.
_proxyOperationManager = _proxyOperationManagerCreator(discoverySources[0], this);
_testHostManager = _proxyOperationManager.TestHostManager;
diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Client/TestRuntimeProviderInfo.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/Client/TestRuntimeProviderInfo.cs
new file mode 100644
index 0000000000..1369e89f55
--- /dev/null
+++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Client/TestRuntimeProviderInfo.cs
@@ -0,0 +1,25 @@
+// 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.Collections.Generic;
+
+using Microsoft.VisualStudio.TestPlatform.ObjectModel;
+
+namespace Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Client;
+
+public class TestRuntimeProviderInfo
+{
+ public Type Type { get; }
+ public bool Shared { get; }
+ public string RunSettings { get; }
+ public List SourceDetails { get; }
+
+ public TestRuntimeProviderInfo(Type type, bool shared, string runSettings, List sourceDetails)
+ {
+ Type = type;
+ Shared = shared;
+ RunSettings = runSettings;
+ SourceDetails = sourceDetails;
+ }
+}
diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/PublicAPI/PublicAPI.Shipped.txt b/src/Microsoft.TestPlatform.CrossPlatEngine/PublicAPI/PublicAPI.Shipped.txt
index 0bbca9f3a6..cec5b51cbd 100644
--- a/src/Microsoft.TestPlatform.CrossPlatEngine/PublicAPI/PublicAPI.Shipped.txt
+++ b/src/Microsoft.TestPlatform.CrossPlatEngine/PublicAPI/PublicAPI.Shipped.txt
@@ -86,13 +86,9 @@ Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Execution.ExecutionManager.I
Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Execution.ExecutionManager.StartTestRun(System.Collections.Generic.Dictionary> adapterSourceMap, string package, string runSettings, Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine.ClientProtocol.TestExecutionContext testExecutionContext, Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine.ITestCaseEventsHandler testCaseEventsHandler, Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.ITestRunEventsHandler runEventsHandler) -> void
Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Execution.ExecutionManager.StartTestRun(System.Collections.Generic.IEnumerable tests, string package, string runSettings, Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine.ClientProtocol.TestExecutionContext testExecutionContext, Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine.ITestCaseEventsHandler testCaseEventsHandler, Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.ITestRunEventsHandler runEventsHandler) -> void
Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.ProxyTestSessionManager
-Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.ProxyTestSessionManager.ProxyTestSessionManager(Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.StartTestSessionCriteria criteria, int testhostCount, System.Func proxyCreator) -> void
Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.TestEngine
-Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.TestEngine.GetDiscoveryManager(Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.IRequestData requestData, Microsoft.VisualStudio.TestPlatform.ObjectModel.Host.ITestRuntimeProvider testHostManager, Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.DiscoveryCriteria discoveryCriteria) -> Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine.IProxyDiscoveryManager
-Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.TestEngine.GetExecutionManager(Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.IRequestData requestData, Microsoft.VisualStudio.TestPlatform.ObjectModel.Host.ITestRuntimeProvider testHostManager, Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.TestRunCriteria testRunCriteria) -> Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine.IProxyExecutionManager
Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.TestEngine.GetExtensionManager() -> Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine.ITestExtensionManager
Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.TestEngine.GetLoggerManager(Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.IRequestData requestData) -> Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine.ITestLoggerManager
-Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.TestEngine.GetTestSessionManager(Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.IRequestData requestData, Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.StartTestSessionCriteria testSessionCriteria) -> Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine.IProxyTestSessionManager
Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.TestEngine.TestEngine() -> void
Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.TestEngine.TestEngine(Microsoft.VisualStudio.TestPlatform.Common.Hosting.TestRuntimeProviderManager testHostProviderManager, Microsoft.VisualStudio.TestPlatform.PlatformAbstractions.Interfaces.IProcessHelper processHelper) -> void
Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.TestExtensionManager
@@ -130,3 +126,14 @@ virtual Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.ProxyTestSessionMana
virtual Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.ProxyTestSessionManager.StopSession(Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.IRequestData requestData) -> bool
Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Client.ProxyDiscoveryManager.Abort(Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.ITestDiscoveryEventsHandler2 eventHandler) -> void
Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Discovery.DiscoveryManager.Abort(Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.ITestDiscoveryEventsHandler2 eventHandler) -> void
+Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Client.TestRuntimeProviderInfo
+Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Client.TestRuntimeProviderInfo.RunSettings.get -> string
+Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Client.TestRuntimeProviderInfo.Shared.get -> bool
+Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Client.TestRuntimeProviderInfo.SourceDetails.get -> System.Collections.Generic.List
+Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Client.TestRuntimeProviderInfo.TestRuntimeProviderInfo(System.Type type, bool shared, string runSettings, System.Collections.Generic.List sourceDetails) -> void
+Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Client.TestRuntimeProviderInfo.Type.get -> System.Type
+Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.TestEngine.GetDiscoveryManager(Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.IRequestData requestData, Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.DiscoveryCriteria discoveryCriteria, System.Collections.Generic.IDictionary sourceToSourceDetailMap) -> Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine.IProxyDiscoveryManager
+Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.TestEngine.GetExecutionManager(Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.IRequestData requestData, Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.TestRunCriteria testRunCriteria, System.Collections.Generic.IDictionary sourceToSourceDetailMap) -> Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine.IProxyExecutionManager
+Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.TestEngine.GetTestSessionManager(Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.IRequestData requestData, Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.StartTestSessionCriteria testSessionCriteria, System.Collections.Generic.IDictionary sourceToSourceDetailMap) -> Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine.IProxyTestSessionManager
+Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.ProxyTestSessionManager.ProxyTestSessionManager(Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.StartTestSessionCriteria criteria, int maxTesthostCount, System.Func proxyCreator, System.Collections.Generic.List runtimeProviders) -> void
+
diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/TestEngine.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/TestEngine.cs
index 20c85c3f2d..f86cae546d 100644
--- a/src/Microsoft.TestPlatform.CrossPlatEngine/TestEngine.cs
+++ b/src/Microsoft.TestPlatform.CrossPlatEngine/TestEngine.cs
@@ -5,6 +5,7 @@
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
+using System.Text;
using Microsoft.VisualStudio.TestPlatform.Common;
using Microsoft.VisualStudio.TestPlatform.Common.Hosting;
@@ -61,28 +62,47 @@ internal TestEngine(
///
public IProxyDiscoveryManager GetDiscoveryManager(
IRequestData requestData,
- ITestRuntimeProvider testHostManager,
- DiscoveryCriteria discoveryCriteria)
+ DiscoveryCriteria discoveryCriteria,
+ IDictionary sourceToSourceDetailMap)
{
+ // Parallel level determines how many processes at most we should start at the same time. We take the number from settings, and if user
+ // has no preference or the preference is 0 then we use the number of logical processors. Or the number of sources, whatever is lower.
+ // We don't know for sure if we will start that many processes as some of the sources can run in a single testhost. This is determined by
+ // Shared on the test runtime provider. At this point we need to know only if the parallel level is more than 1, and so if we will do parallel
+ // run or not.
var parallelLevel = VerifyParallelSettingAndCalculateParallelLevel(
discoveryCriteria.Sources.Count(),
discoveryCriteria.RunSettings);
+ var isParallelRun = parallelLevel > 1;
+
// Collecting IsParallel enabled.
- requestData.MetricsCollection.Add(
- TelemetryDataConstants.ParallelEnabledDuringDiscovery,
- parallelLevel > 1 ? "True" : "False");
- requestData.MetricsCollection.Add(
- TelemetryDataConstants.TestSessionId,
- discoveryCriteria.TestSessionInfo?.Id.ToString() ?? string.Empty);
+ requestData.MetricsCollection.Add(TelemetryDataConstants.ParallelEnabledDuringDiscovery, isParallelRun ? "True" : "False");
+ requestData.MetricsCollection.Add(TelemetryDataConstants.TestSessionId, discoveryCriteria.TestSessionInfo?.Id.ToString() ?? string.Empty);
+
+ // Get testhost managers by configuration, and either use it for in-process run. or for single source run.
+ List testHostManagers = GetTestRuntimeProvidersForUniqueConfigurations(discoveryCriteria.RunSettings, sourceToSourceDetailMap, out ITestRuntimeProvider testHostManager);
- if (ShouldRunInNoIsolation(discoveryCriteria.RunSettings, parallelLevel > 1, false))
+ // This is a big if that figures out if we can run in process. In process run is very restricted, it is non-parallel run
+ // that has the same target framework as the current process, and it also must not be running in DesignMode (server mode / under IDE)
+ // and more conditions. In all other cases we run in a separate testhost process.
+ if (ShouldRunInProcess(discoveryCriteria.RunSettings, isParallelRun, isDataCollectorEnabled: false, testHostManagers))
{
+ // We are running in process, so whatever the architecture and framework that was figured out is, it must be compatible. If we have more
+ // changes that we want to do to runsettings in the future, based on SourceDetail then it will depend on those details. But in general
+ // we will have to check that all source details are the same. Otherwise we for sure cannot run in process.
+ // E.g. if we get list of sources where one of them has different architecture we for sure cannot run in process, because the current
+ // process can handle only single runsettings.
+ if (testHostManagers.Count != 1)
+ {
+ throw new InvalidOperationException($"Exactly 1 testhost manager must be provided when running in process, but there {testHostManagers.Count} were provided.");
+ }
+ var testHostManagerInfo = testHostManagers[0];
+ testHostManager.Initialize(TestSessionMessageLogger.Instance, testHostManagerInfo.RunSettings);
+
var isTelemetryOptedIn = requestData.IsTelemetryOptedIn;
var newRequestData = GetRequestData(isTelemetryOptedIn);
- return new InProcessProxyDiscoveryManager(
- testHostManager,
- new TestHostManagerFactory(newRequestData));
+ return new InProcessProxyDiscoveryManager(testHostManager, new TestHostManagerFactory(newRequestData));
}
// Create one data aggregator per parallel discovery and share it with all the proxy discovery managers.
@@ -91,10 +111,13 @@ public IProxyDiscoveryManager GetDiscoveryManager(
// discovery manager to publish its current state. But doing so we are losing the collected state of all the
// other managers.
var discoveryDataAggregator = new DiscoveryDataAggregator();
- Func proxyDiscoveryManagerCreator = () =>
+ Func proxyDiscoveryManagerCreator = runtimeProviderInfo =>
{
- var hostManager = _testHostProviderManager.GetTestHostManagerByRunConfiguration(discoveryCriteria.RunSettings);
- hostManager?.Initialize(TestSessionMessageLogger.Instance, discoveryCriteria.RunSettings);
+ var sources = runtimeProviderInfo.SourceDetails.Select(r => r.Source).ToList();
+ var hostManager = _testHostProviderManager.GetTestHostManagerByRunConfiguration(runtimeProviderInfo.RunSettings, sources);
+ hostManager?.Initialize(TestSessionMessageLogger.Instance, runtimeProviderInfo.RunSettings);
+
+ ThrowExceptionIfTestHostManagerIsNull(hostManager, runtimeProviderInfo.RunSettings);
// This function is used to either take a pre-existing proxy operation manager from
// the test pool or to create a new proxy operation manager on the spot.
@@ -108,7 +131,7 @@ public IProxyDiscoveryManager GetDiscoveryManager(
var proxyOperationManager = TestSessionPool.Instance.TryTakeProxy(
discoveryCriteria.TestSessionInfo,
source,
- discoveryCriteria.RunSettings);
+ runtimeProviderInfo.RunSettings);
if (proxyOperationManager == null)
{
@@ -144,43 +167,54 @@ public IProxyDiscoveryManager GetDiscoveryManager(
discoveryDataAggregator);
};
- return (parallelLevel > 1 || !testHostManager.Shared)
- ? new ParallelProxyDiscoveryManager(
- requestData,
- proxyDiscoveryManagerCreator,
- discoveryDataAggregator,
- parallelLevel,
- sharedHosts: testHostManager.Shared)
- : proxyDiscoveryManagerCreator();
+ return new ParallelProxyDiscoveryManager(requestData, proxyDiscoveryManagerCreator, discoveryDataAggregator, parallelLevel, testHostManagers);
}
///
public IProxyExecutionManager GetExecutionManager(
IRequestData requestData,
- ITestRuntimeProvider testHostManager,
- TestRunCriteria testRunCriteria)
+ TestRunCriteria testRunCriteria,
+ IDictionary sourceToSourceDetailMap)
{
+ // We use mulitple "different" runsettings here. We have runsettings that come with the testRunCriteria,
+ // and we use that to figure out the common stuff before we try to setup the run. Later we patch the settings
+ // from the additional details that were passed. Those should not affect the common properties that are used for setup.
+ // Right now the only two things that change there are the architecture and framework so we can mix them in a single run.
var distinctSources = GetDistinctNumberOfSources(testRunCriteria);
- var parallelLevel = VerifyParallelSettingAndCalculateParallelLevel(
- distinctSources,
- testRunCriteria.TestRunSettings);
+ var parallelLevel = VerifyParallelSettingAndCalculateParallelLevel(distinctSources, testRunCriteria.TestRunSettings);
+
+ // See comments in GetDiscoveryManager for more info about what is happening in this method.
+ var isParallelRun = parallelLevel > 1;
// Collecting IsParallel enabled.
- requestData.MetricsCollection.Add(
- TelemetryDataConstants.ParallelEnabledDuringExecution,
- parallelLevel > 1 ? "True" : "False");
- requestData.MetricsCollection.Add(
- TelemetryDataConstants.TestSessionId,
- testRunCriteria.TestSessionInfo?.Id.ToString() ?? string.Empty);
+ requestData.MetricsCollection.Add(TelemetryDataConstants.ParallelEnabledDuringExecution, isParallelRun ? "True" : "False");
+ requestData.MetricsCollection.Add(TelemetryDataConstants.TestSessionId, testRunCriteria.TestSessionInfo?.Id.ToString() ?? string.Empty);
var isDataCollectorEnabled = XmlRunSettingsUtilities.IsDataCollectionEnabled(testRunCriteria.TestRunSettings);
var isInProcDataCollectorEnabled = XmlRunSettingsUtilities.IsInProcDataCollectionEnabled(testRunCriteria.TestRunSettings);
- if (ShouldRunInNoIsolation(
+ var testHostProviders = GetTestRuntimeProvidersForUniqueConfigurations(testRunCriteria.TestRunSettings, sourceToSourceDetailMap, out ITestRuntimeProvider testHostManager);
+
+ if (ShouldRunInProcess(
testRunCriteria.TestRunSettings,
- parallelLevel > 1,
- isDataCollectorEnabled || isInProcDataCollectorEnabled))
+ isParallelRun,
+ isDataCollectorEnabled || isInProcDataCollectorEnabled,
+ testHostProviders))
{
+ // Not updating runsettings from source detail on purpose here. We are running in process, so whatever the settings we figured out at the start. They must be compatible
+ // with the current process, otherwise we would not be able to run inside of the current process.
+ //
+ // We know that we only have a single testHostManager here, because we figure that out in ShouldRunInProcess.
+ ThrowExceptionIfTestHostManagerIsNull(testHostManager, testRunCriteria.TestRunSettings);
+
+ testHostManager.Initialize(TestSessionMessageLogger.Instance, testRunCriteria.TestRunSettings);
+
+ // NOTE: The custom launcher should not be set when we have test session info available.
+ if (testRunCriteria.TestHostLauncher != null)
+ {
+ testHostManager.SetCustomLauncher(testRunCriteria.TestHostLauncher);
+ }
+
var isTelemetryOptedIn = requestData.IsTelemetryOptedIn;
var newRequestData = GetRequestData(isTelemetryOptedIn);
return new InProcessProxyExecutionManager(
@@ -188,134 +222,140 @@ public IProxyExecutionManager GetExecutionManager(
new TestHostManagerFactory(newRequestData));
}
+ // This creates a single non-parallel execution manager, based requestData, isDataCollectorEnabled and the
+ // overall testRunCriteria. The overall testRunCriteria are split to smaller pieces (e.g. each source from the overall
+ // testRunCriteria) so we can run them in parallel, and those are then passed to those non-parallel execution managers.
+ //
+ // The function below grabs most of the parameter via closure from the local context,
+ // but gets the runtime provider later, because that is specific info to the source (or sources) it will be running.
+ // This creator does not get those smaller pieces of testRunCriteria, those come later when we call a method on
+ // the non-parallel execution manager we create here. E.g. StartTests().
+ Func proxyExecutionManagerCreator = runtimeProviderInfo =>
+ CreateNonParallelExecutionManager(requestData, testRunCriteria, isDataCollectorEnabled, runtimeProviderInfo);
+
+ var executionManager = new ParallelProxyExecutionManager(requestData, proxyExecutionManagerCreator, parallelLevel, testHostProviders);
+
+ EqtTrace.Verbose($"TestEngine.GetExecutionManager: Chosen execution manager '{executionManager.GetType().AssemblyQualifiedName}' ParallelLevel '{parallelLevel}'.");
+
+ return executionManager;
+ }
+
+ // This is internal so tests can use it.
+ internal IProxyExecutionManager CreateNonParallelExecutionManager(IRequestData requestData, TestRunCriteria testRunCriteria, bool isDataCollectorEnabled, TestRuntimeProviderInfo runtimeProviderInfo)
+ {
// SetupChannel ProxyExecutionManager with data collection if data collectors are
// specified in run settings.
- Func proxyExecutionManagerCreator = () =>
+ // Create a new host manager, to be associated with individual
+ // ProxyExecutionManager(&POM)
+ var sources = runtimeProviderInfo.SourceDetails.Select(r => r.Source).ToList();
+ var hostManager = _testHostProviderManager.GetTestHostManagerByRunConfiguration(runtimeProviderInfo.RunSettings, sources);
+ ThrowExceptionIfTestHostManagerIsNull(hostManager, runtimeProviderInfo.RunSettings);
+ hostManager.Initialize(TestSessionMessageLogger.Instance, runtimeProviderInfo.RunSettings);
+
+ if (testRunCriteria.TestHostLauncher != null)
{
- // Create a new host manager, to be associated with individual
- // ProxyExecutionManager(&POM)
- var hostManager = _testHostProviderManager.GetTestHostManagerByRunConfiguration(testRunCriteria.TestRunSettings);
- hostManager?.Initialize(TestSessionMessageLogger.Instance, testRunCriteria.TestRunSettings);
+ hostManager.SetCustomLauncher(testRunCriteria.TestHostLauncher);
+ }
- if (testRunCriteria.TestHostLauncher != null)
- {
- hostManager.SetCustomLauncher(testRunCriteria.TestHostLauncher);
- }
+ var requestSender = new TestRequestSender(requestData.ProtocolConfig, hostManager);
- var requestSender = new TestRequestSender(requestData.ProtocolConfig, hostManager);
+ if (testRunCriteria.TestSessionInfo != null)
+ {
+ // This function is used to either take a pre-existing proxy operation manager from
+ // the test pool or to create a new proxy operation manager on the spot.
+ Func
+ proxyOperationManagerCreator = (
+ string source,
+ ProxyExecutionManager proxyExecutionManager) =>
+ {
+ var proxyOperationManager = TestSessionPool.Instance.TryTakeProxy(
+ testRunCriteria.TestSessionInfo,
+ source,
+ runtimeProviderInfo.RunSettings);
- if (testRunCriteria.TestSessionInfo != null)
- {
- // This function is used to either take a pre-existing proxy operation manager from
- // the test pool or to create a new proxy operation manager on the spot.
- Func
- proxyOperationManagerCreator = (
- string source,
- ProxyExecutionManager proxyExecutionManager) =>
+ if (proxyOperationManager == null)
{
- var proxyOperationManager = TestSessionPool.Instance.TryTakeProxy(
- testRunCriteria.TestSessionInfo,
- source,
- testRunCriteria.TestRunSettings);
-
- if (proxyOperationManager == null)
- {
- // If the proxy creation process based on test session info failed, then
- // we'll proceed with the normal creation process as if no test session
- // info was passed in in the first place.
- //
- // WARNING: This should not normally happen and it raises questions
- // regarding the test session pool operation and consistency.
- EqtTrace.Warning("ProxyExecutionManager creation with test session failed.");
-
- proxyOperationManager = new ProxyOperationManager(
- requestData,
- requestSender,
- hostManager,
- proxyExecutionManager);
- }
-
- return proxyOperationManager;
- };
-
- // In case we have an active test session, data collection needs were
- // already taken care of when first creating the session. As a consequence
- // we always return this proxy instead of choosing between the vanilla
- // execution proxy and the one with data collection enabled.
- return new ProxyExecutionManager(
- testRunCriteria.TestSessionInfo,
- proxyOperationManagerCreator,
- testRunCriteria.DebugEnabledForTestSession);
- }
+ // If the proxy creation process based on test session info failed, then
+ // we'll proceed with the normal creation process as if no test session
+ // info was passed in in the first place.
+ //
+ // WARNING: This should not normally happen and it raises questions
+ // regarding the test session pool operation and consistency.
+ EqtTrace.Warning("ProxyExecutionManager creation with test session failed.");
- return isDataCollectorEnabled
- ? new ProxyExecutionManagerWithDataCollection(
- requestData,
- requestSender,
- hostManager,
- new ProxyDataCollectionManager(
- requestData,
- testRunCriteria.TestRunSettings,
- GetSourcesFromTestRunCriteria(testRunCriteria)))
- : new ProxyExecutionManager(
- requestData,
- requestSender,
- hostManager);
- };
+ proxyOperationManager = new ProxyOperationManager(
+ requestData,
+ requestSender,
+ hostManager,
+ proxyExecutionManager);
+ }
- // parallelLevel = 1 for desktop should go via else route.
- var executionManager = (parallelLevel > 1 || !testHostManager.Shared)
- ? new ParallelProxyExecutionManager(
- requestData,
- proxyExecutionManagerCreator,
- parallelLevel,
- sharedHosts: testHostManager.Shared)
- : proxyExecutionManagerCreator();
+ return proxyOperationManager;
+ };
- EqtTrace.Verbose($"TestEngine.GetExecutionManager: Chosen execution manager '{executionManager.GetType().AssemblyQualifiedName}' ParallelLevel '{parallelLevel}' Shared host '{testHostManager.Shared}'");
+ // In case we have an active test session, data collection needs were
+ // already taken care of when first creating the session. As a consequence
+ // we always return this proxy instead of choosing between the vanilla
+ // execution proxy and the one with data collection enabled.
+ return new ProxyExecutionManager(
+ testRunCriteria.TestSessionInfo,
+ proxyOperationManagerCreator,
+ testRunCriteria.DebugEnabledForTestSession);
+ }
- return executionManager;
+ return isDataCollectorEnabled
+ ? new ProxyExecutionManagerWithDataCollection(
+ requestData,
+ requestSender,
+ hostManager,
+ new ProxyDataCollectionManager(
+ requestData,
+ runtimeProviderInfo.RunSettings,
+ sources))
+ : new ProxyExecutionManager(
+ requestData,
+ requestSender,
+ hostManager);
}
///
public IProxyTestSessionManager GetTestSessionManager(
IRequestData requestData,
- StartTestSessionCriteria testSessionCriteria)
+ StartTestSessionCriteria testSessionCriteria,
+ IDictionary sourceToSourceDetailMap)
{
var parallelLevel = VerifyParallelSettingAndCalculateParallelLevel(
testSessionCriteria.Sources.Count,
testSessionCriteria.RunSettings);
+ bool isParallelRun = parallelLevel > 1;
requestData.MetricsCollection.Add(
TelemetryDataConstants.ParallelEnabledDuringStartTestSession,
- parallelLevel > 1 ? "True" : "False");
+ isParallelRun ? "True" : "False");
var isDataCollectorEnabled = XmlRunSettingsUtilities.IsDataCollectionEnabled(testSessionCriteria.RunSettings);
var isInProcDataCollectorEnabled = XmlRunSettingsUtilities.IsInProcDataCollectionEnabled(testSessionCriteria.RunSettings);
- if (ShouldRunInNoIsolation(
+ List testRuntimeProviders = GetTestRuntimeProvidersForUniqueConfigurations(testSessionCriteria.RunSettings, sourceToSourceDetailMap, out var _);
+
+ if (ShouldRunInProcess(
testSessionCriteria.RunSettings,
- parallelLevel > 1,
- isDataCollectorEnabled || isInProcDataCollectorEnabled))
+ isParallelRun,
+ isDataCollectorEnabled || isInProcDataCollectorEnabled,
+ testRuntimeProviders))
{
- // This condition is the equivalent of the in-process proxy execution manager case.
- // In this case all tests will be run in the vstest.console process, so there's no
- // test host to be started. As a consequence there'll be no session info.
+ // In this case all tests will be run in the current process (vstest.console), so there is no
+ // testhost to pre-start. No session will be created, and the session info will be null.
return null;
}
- Func proxyCreator = () =>
+ Func proxyCreator = testRuntimeProviderInfo =>
{
- var hostManager = _testHostProviderManager.GetTestHostManagerByRunConfiguration(testSessionCriteria.RunSettings);
- if (hostManager == null)
- {
- throw new TestPlatformException(
- string.Format(
- CultureInfo.CurrentCulture,
- Resources.Resources.NoTestHostProviderFound));
- }
+ var sources = testRuntimeProviderInfo.SourceDetails.Select(x => x.Source).ToList();
+ var hostManager = _testHostProviderManager.GetTestHostManagerByRunConfiguration(testRuntimeProviderInfo.RunSettings, sources);
+ ThrowExceptionIfTestHostManagerIsNull(hostManager, testRuntimeProviderInfo.RunSettings);
- hostManager.Initialize(TestSessionMessageLogger.Instance, testSessionCriteria.RunSettings);
+ hostManager.Initialize(TestSessionMessageLogger.Instance, testRuntimeProviderInfo.RunSettings);
if (testSessionCriteria.TestHostLauncher != null)
{
hostManager.SetCustomLauncher(testSessionCriteria.TestHostLauncher);
@@ -344,7 +384,7 @@ public IProxyTestSessionManager GetTestSessionManager(
// hostManager,
// new ProxyDataCollectionManager(
// requestData,
- // testSessionCriteria.RunSettings,
+ // runsettingsXml,
// testSessionCriteria.Sources))
// {
// CloseRequestSenderChannelOnProxyClose = true
@@ -355,13 +395,40 @@ public IProxyTestSessionManager GetTestSessionManager(
hostManager);
};
- var testhostManager = _testHostProviderManager.GetTestHostManagerByRunConfiguration(testSessionCriteria.RunSettings);
- testhostManager.Initialize(TestSessionMessageLogger.Instance, testSessionCriteria.RunSettings);
- var testhostCount = (parallelLevel > 1 || !testhostManager.Shared)
- ? testSessionCriteria.Sources.Count
- : 1;
+ // TODO: This condition should be returning the maxParallel level to avoid pre-starting way too many testhosts, because maxParallel level,
+ // can be smaller than the number of sources to run.
+ var maxTesthostCount = isParallelRun ? testSessionCriteria.Sources.Count : 1;
- return new ProxyTestSessionManager(testSessionCriteria, testhostCount, proxyCreator);
+ return new ProxyTestSessionManager(testSessionCriteria, maxTesthostCount, proxyCreator, testRuntimeProviders);
+ }
+
+ private List GetTestRuntimeProvidersForUniqueConfigurations(
+ string runSettings,
+ IDictionary sourceToSourceDetailMap,
+ out ITestRuntimeProvider mostRecentlyCreatedInstance)
+ {
+ // Group source details to get unique frameworks and architectures for which we will run, so we can figure
+ // out which runtime providers would run them, and if the runtime provider is shared or not.
+ mostRecentlyCreatedInstance = null;
+ var testRuntimeProviders = new List();
+ var uniqueRunConfigurations = sourceToSourceDetailMap.Values.GroupBy(k => $"{k.Framework}|{k.Architecture}");
+ foreach (var runConfiguration in uniqueRunConfigurations)
+ {
+ // It is okay to take the first (or any) source detail in the group. We are grouping to get the same source detail, so all architectures and frameworks are the same.
+ var sourceDetail = runConfiguration.First();
+ var runsettingsXml = SourceDetailHelper.UpdateRunSettingsFromSourceDetail(runSettings, sourceDetail);
+ var sources = runConfiguration.Select(c => c.Source).ToList();
+ // TODO: We could improve the implementation by adding an overload that won't create a new instance always, because we only need to know the Type.
+ var testRuntimeProvider = _testHostProviderManager.GetTestHostManagerByRunConfiguration(runsettingsXml, sources);
+ var testRuntimeProviderInfo = new TestRuntimeProviderInfo(testRuntimeProvider.GetType(), testRuntimeProvider.Shared, runsettingsXml, sourceDetails: runConfiguration.ToList());
+
+ // Outputting the instance, because the code for in-process run uses it, and we don't want to resolve it another time.
+ mostRecentlyCreatedInstance = testRuntimeProvider;
+ testRuntimeProviders.Add(testRuntimeProviderInfo);
+ }
+
+ ThrowExceptionIfAnyTestHostManagerIsNullOrNoneAreFound(testRuntimeProviders);
+ return testRuntimeProviders;
}
///
@@ -408,6 +475,7 @@ private int VerifyParallelSettingAndCalculateParallelLevel(
// Check the user parallel setting.
int userParallelSetting = RunSettingsUtilities.GetMaxCpuCount(runSettings);
parallelLevelToUse = userParallelSetting == 0
+ // TODO: use environment helper so we can control this from tests.
? Environment.ProcessorCount
: userParallelSetting;
var enableParallel = parallelLevelToUse > 1;
@@ -445,11 +513,18 @@ private int VerifyParallelSettingAndCalculateParallelLevel(
return parallelLevelToUse;
}
- private bool ShouldRunInNoIsolation(
+ private bool ShouldRunInProcess(
string runsettings,
bool isParallelEnabled,
- bool isDataCollectorEnabled)
+ bool isDataCollectorEnabled,
+ List testHostProviders)
{
+ if (testHostProviders.Count > 1)
+ {
+ EqtTrace.Info("TestEngine.ShouldRunInNoIsolation: This run has multiple different architectures or frameworks, running in isolation (in a separate testhost proces).");
+ return false;
+ }
+
var runConfiguration = XmlRunSettingsUtilities.GetRunConfigurationNode(runsettings);
if (runConfiguration.InIsolation)
@@ -512,15 +587,32 @@ private IRequestData GetRequestData(bool isTelemetryOptedIn)
};
}
- ///
- /// Gets test sources from test run criteria.
- ///
- ///
- /// The test sources.
- private IEnumerable GetSourcesFromTestRunCriteria(TestRunCriteria testRunCriteria)
+ private static void ThrowExceptionIfTestHostManagerIsNull(ITestRuntimeProvider testHostManager, string settingsXml)
+ {
+ if (testHostManager == null)
+ {
+ EqtTrace.Error($"{nameof(TestEngine)}.{nameof(ThrowExceptionIfTestHostManagerIsNull)}: No suitable testHostProvider found for runsettings: {settingsXml}");
+ throw new TestPlatformException(string.Format(CultureInfo.CurrentCulture, Resources.Resources.NoTestHostProviderFound));
+ }
+ }
+
+ private static void ThrowExceptionIfAnyTestHostManagerIsNullOrNoneAreFound(List testRuntimeProviders)
{
- return testRunCriteria.HasSpecificTests
- ? TestSourcesUtility.GetSources(testRunCriteria.Tests)
- : testRunCriteria.Sources;
+ if (!testRuntimeProviders.Any())
+ throw new ArgumentException(null, nameof(testRuntimeProviders));
+
+ var missingRuntimeProviders = testRuntimeProviders.Where(p => p.Type == null);
+ if (missingRuntimeProviders.Any())
+ {
+ var stringBuilder = new StringBuilder();
+ stringBuilder.AppendLine(string.Format(CultureInfo.CurrentCulture, Resources.Resources.NoTestHostProviderFound));
+ foreach (var missingRuntimeProvider in missingRuntimeProviders)
+ {
+ EqtTrace.Error($"{nameof(TestEngine)}.{nameof(ThrowExceptionIfAnyTestHostManagerIsNullOrNoneAreFound)}: No suitable testHostProvider found for sources {missingRuntimeProvider.SourceDetails.Select(s => s.Source)} and runsettings: {missingRuntimeProvider.RunSettings}");
+ missingRuntimeProvider.SourceDetails.ForEach(detail => stringBuilder.AppendLine(detail.Source));
+ }
+
+ throw new TestPlatformException(stringBuilder.ToString());
+ }
}
}
diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/TestSession/ProxyTestSessionManager.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/TestSession/ProxyTestSessionManager.cs
index 2ed0607d9d..4bc8f33b17 100644
--- a/src/Microsoft.TestPlatform.CrossPlatEngine/TestSession/ProxyTestSessionManager.cs
+++ b/src/Microsoft.TestPlatform.CrossPlatEngine/TestSession/ProxyTestSessionManager.cs
@@ -39,12 +39,14 @@ private enum TestSessionState
private readonly object _proxyOperationLockObject = new();
private volatile bool _proxySetupFailed;
private readonly StartTestSessionCriteria _testSessionCriteria;
- private readonly int _testhostCount;
+ private readonly int _maxTesthostCount;
private TestSessionInfo _testSessionInfo;
- private readonly Func _proxyCreator;
+ private readonly Func _proxyCreator;
+ private readonly List _runtimeProviders;
private readonly IList _proxyContainerList;
private readonly IDictionary _proxyMap;
private readonly Stopwatch _testSessionStopwatch;
+ private readonly Dictionary _sourceToRuntimeProviderInfoMap;
private IDictionary _testSessionEnvironmentVariables = new Dictionary();
private IDictionary TestSessionEnvironmentVariables
@@ -67,20 +69,27 @@ private IDictionary TestSessionEnvironmentVariables
///
///
/// The test session criteria.
- /// The testhost count.
+ /// The testhost count.
/// The proxy creator.
public ProxyTestSessionManager(
StartTestSessionCriteria criteria,
- int testhostCount,
- Func proxyCreator)
+ int maxTesthostCount,
+ Func proxyCreator,
+ List runtimeProviders)
{
_testSessionCriteria = criteria;
- _testhostCount = testhostCount;
+ _maxTesthostCount = maxTesthostCount;
_proxyCreator = proxyCreator;
-
+ _runtimeProviders = runtimeProviders;
_proxyContainerList = new List();
_proxyMap = new Dictionary();
_testSessionStopwatch = new Stopwatch();
+
+ // Get dictionary from source -> runtimeProviderInfo, that has the type of runtime provider to create for this
+ // source, and updated runsettings.
+ _sourceToRuntimeProviderInfoMap = _runtimeProviders
+ .SelectMany(runtimeProviderInfo => runtimeProviderInfo.SourceDetails.Select(detail => new KeyValuePair(detail.Source, runtimeProviderInfo)))
+ .ToDictionary(pair => pair.Key, pair => pair.Value);
}
// NOTE: The method is virtual for mocking purposes.
@@ -99,33 +108,28 @@ public virtual bool StartSession(ITestSessionEventsHandler eventsHandler, IReque
var stopwatch = new Stopwatch();
stopwatch.Start();
+ // TODO: Right now we either pre-create 1 testhost if parallel is disabled, or we pre-create as many
+ // testhosts as we have sources. In the future we will have a maxParallelLevel set to the actual parallel level
+ // (which might be lower than the number of sources) and we should do some kind of thinking here to figure out how to split the sources.
+ // To follow the way parallel execution and discovery is (supposed to be) working, there should be as many testhosts
+ // as the maxParallel level pre-started, and marked with the Shared, and configuration that they can run.
+
// Create all the proxies in parallel, one task per proxy.
- var taskList = new Task[_testhostCount];
+ var taskList = new Task[_maxTesthostCount];
for (int i = 0; i < taskList.Length; ++i)
{
- // The testhost count is equal to 1 because one of the following conditions
- // holds true:
- // 1. we're dealing with a shared testhost (e.g.: .NET Framework testhost)
- // that must process multiple sources within the same testhost process;
- // 2. we're dealing with a single testhost (shared or not, it doesn't matter)
- // that must process a single source;
- // Either way, no further processing of the original test source list is needed
- // in either of those cases.
- //
- // Consequentely, if the testhost count is greater than one it means that the
- // testhost is not shared (e.g.: .NET Core testhost), in which case each test
- // source must be processed by a dedicated testhost, which is the reason we
- // create a list with a single element, i.e. the current source to be processed.
- var sources = (_testhostCount == 1)
- ? _testSessionCriteria.Sources
- : new List() { _testSessionCriteria.Sources[i] };
+ // This is similar to what we do in ProxyExecutionManager, and ProxyDiscoveryManager, we split
+ // up the payload into multiple smaller pieces. Here it is one source per proxy.
+ var source = _testSessionCriteria.Sources[i];
+ var sources = new List() { source };
+ var runtimeProviderInfo = _sourceToRuntimeProviderInfoMap[source];
taskList[i] = Task.Factory.StartNew(() =>
{
- if (!SetupRawProxy(
- sources,
- _testSessionCriteria.RunSettings))
+ var proxySetupSucceeded = SetupRawProxy(sources, runtimeProviderInfo);
+ if (!proxySetupSucceeded)
{
+ // Set this only in the failed case, so we can check if any proxy failed to setup.
_proxySetupFailed = true;
}
});
@@ -248,6 +252,7 @@ public virtual ProxyOperationManager DequeueProxy(string source, string runSetti
// its own proxy instead.
if (!CheckRunSettingsAreCompatible(runSettings))
{
+ EqtTrace.Verbose($"ProxyTestSessionManager.DequeueProxy: A proxy exists, but the runsettings do not match. Skipping it. Incoming settings: {runSettings}, Settings on proxy: {_testSessionCriteria.RunSettings}");
throw new InvalidOperationException(
string.Format(
CultureInfo.CurrentUICulture,
@@ -328,12 +333,12 @@ private int EnqueueNewProxy(
private bool SetupRawProxy(
IList sources,
- string runSettings)
+ TestRuntimeProviderInfo runtimeProviderInfo)
{
try
{
// Create and cache the proxy.
- var operationManagerProxy = _proxyCreator();
+ var operationManagerProxy = _proxyCreator(runtimeProviderInfo);
if (operationManagerProxy == null)
{
return false;
@@ -343,7 +348,7 @@ private bool SetupRawProxy(
operationManagerProxy.Initialize(skipDefaultAdapters: false);
// Start the test host associated to the proxy.
- if (!operationManagerProxy.SetupChannel(sources, runSettings))
+ if (!operationManagerProxy.SetupChannel(sources, runtimeProviderInfo.RunSettings))
{
return false;
}
diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Utilities/SourceDetailHelper.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/Utilities/SourceDetailHelper.cs
new file mode 100644
index 0000000000..b08c94d482
--- /dev/null
+++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Utilities/SourceDetailHelper.cs
@@ -0,0 +1,29 @@
+// 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.IO;
+using System.Xml;
+
+using Microsoft.VisualStudio.TestPlatform.ObjectModel;
+using Microsoft.VisualStudio.TestPlatform.ObjectModel.Utilities;
+using Microsoft.VisualStudio.TestPlatform.Utilities;
+
+namespace Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Utilities;
+
+internal static class SourceDetailHelper
+{
+ internal static string UpdateRunSettingsFromSourceDetail(string runSettings, SourceDetail sourceDetail)
+ {
+ using var stream = new StringReader(runSettings);
+ using var reader = XmlReader.Create(stream, XmlRunSettingsUtilities.ReaderSettings);
+ var document = new XmlDocument();
+ document.Load(reader);
+ var navigator = document.CreateNavigator();
+
+ InferRunSettingsHelper.UpdateTargetFramework(document, sourceDetail.Framework.ToString(), overwrite: true);
+ InferRunSettingsHelper.UpdateTargetPlatform(document, sourceDetail.Architecture.ToString(), overwrite: true);
+
+ var updatedRunSettings = navigator.OuterXml;
+ return updatedRunSettings;
+ }
+}
diff --git a/src/Microsoft.TestPlatform.Execution.Shared/DebuggerBreakpoint.cs b/src/Microsoft.TestPlatform.Execution.Shared/DebuggerBreakpoint.cs
index aff5059c8f..e7db29756d 100644
--- a/src/Microsoft.TestPlatform.Execution.Shared/DebuggerBreakpoint.cs
+++ b/src/Microsoft.TestPlatform.Execution.Shared/DebuggerBreakpoint.cs
@@ -42,11 +42,11 @@ internal static void AttachVisualStudioDebugger(string environmentVariable)
if (vsPid == null)
{
- ConsoleOutput.Instance.WriteLine("Attaching Visual Studio, either a parent or the one that was started first... To specify a VS instance to use, use the PID in the option, instead of 1. No breakpoints are automatically set.", OutputLevel.Information);
+ ConsoleOutput.Instance.WriteLine("Attaching Visual Studio, either a parent or the one that was started first... To specify a VS instance to use, use the PID in the option, instead of 1.", OutputLevel.Information);
}
else
{
- ConsoleOutput.Instance.WriteLine($"Attaching Visual Studio with PID {vsPid} to the process '{Process.GetCurrentProcess().ProcessName}({Process.GetCurrentProcess().Id})'... No breakpoints are automatically set.", OutputLevel.Information);
+ ConsoleOutput.Instance.WriteLine($"Attaching Visual Studio with PID {vsPid} to the process '{Process.GetCurrentProcess().ProcessName}({Process.GetCurrentProcess().Id})'...", OutputLevel.Information);
}
AttachVs(Process.GetCurrentProcess(), vsPid);
diff --git a/src/Microsoft.TestPlatform.ObjectModel/Client/Interfaces/ITestHostLauncher2.cs b/src/Microsoft.TestPlatform.ObjectModel/Client/Interfaces/ITestHostLauncher2.cs
index 5f16818c44..91db204e1e 100644
--- a/src/Microsoft.TestPlatform.ObjectModel/Client/Interfaces/ITestHostLauncher2.cs
+++ b/src/Microsoft.TestPlatform.ObjectModel/Client/Interfaces/ITestHostLauncher2.cs
@@ -1,6 +1,7 @@
// 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.Threading;
#nullable disable
@@ -27,3 +28,18 @@ public interface ITestHostLauncher2 : ITestHostLauncher
/// if the debugger was successfully attached to the requested process, otherwise.
bool AttachDebuggerToProcess(int pid, CancellationToken cancellationToken);
}
+
+[Obsolete("Do not use this api, it is not ready yet.")]
+public interface ITestHostLauncher3 : ITestHostLauncher2
+{
+ bool AttachDebuggerToProcess(AttachDebuggerInfo attachDebuggerInfo);
+}
+
+[Obsolete("Do not use this api, it is not ready yet.")]
+public class AttachDebuggerInfo
+{
+ public Version Version { get; set; }
+ public int ProcessId { get; set; }
+ public Framework TargetFramework { get; set; }
+ public CancellationToken CancellationToken { get; set; }
+}
diff --git a/src/Microsoft.TestPlatform.ObjectModel/Client/Interfaces/ITestPlatform.cs b/src/Microsoft.TestPlatform.ObjectModel/Client/Interfaces/ITestPlatform.cs
index c91c1ff6f2..a58e5cbad5 100644
--- a/src/Microsoft.TestPlatform.ObjectModel/Client/Interfaces/ITestPlatform.cs
+++ b/src/Microsoft.TestPlatform.ObjectModel/Client/Interfaces/ITestPlatform.cs
@@ -45,7 +45,8 @@ void UpdateExtensions(
IDiscoveryRequest CreateDiscoveryRequest(
IRequestData requestData,
DiscoveryCriteria discoveryCriteria,
- TestPlatformOptions options);
+ TestPlatformOptions options,
+ Dictionary sourceToSourceDetailMap);
///
/// Creates a test run request.
@@ -59,7 +60,8 @@ IDiscoveryRequest CreateDiscoveryRequest(
ITestRunRequest CreateTestRunRequest(
IRequestData requestData,
TestRunCriteria testRunCriteria,
- TestPlatformOptions options);
+ TestPlatformOptions options,
+ Dictionary sourceToSourceDetailMap);
///
/// Starts a test session.
@@ -75,5 +77,6 @@ ITestRunRequest CreateTestRunRequest(
bool StartTestSession(
IRequestData requestData,
StartTestSessionCriteria criteria,
- ITestSessionEventsHandler eventsHandler);
+ ITestSessionEventsHandler eventsHandler,
+ Dictionary sourceToSourceDetailMap);
}
diff --git a/src/Microsoft.TestPlatform.ObjectModel/Friends.cs b/src/Microsoft.TestPlatform.ObjectModel/Friends.cs
index ed2a57ef52..4e58caefe6 100644
--- a/src/Microsoft.TestPlatform.ObjectModel/Friends.cs
+++ b/src/Microsoft.TestPlatform.ObjectModel/Friends.cs
@@ -13,6 +13,7 @@
[assembly: InternalsVisibleTo("vstest.console.arm64, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")]
[assembly: InternalsVisibleTo("Microsoft.VisualStudio.TestPlatform.Client, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")]
[assembly: InternalsVisibleTo("Microsoft.TestPlatform.ObjectModel.UnitTests, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")]
+[assembly: InternalsVisibleTo("Microsoft.TestPlatform.CrossPlatEngine.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")]
diff --git a/src/Microsoft.TestPlatform.ObjectModel/PublicAPI/PublicAPI.Shipped.txt b/src/Microsoft.TestPlatform.ObjectModel/PublicAPI/PublicAPI.Shipped.txt
index b3728799c9..691575a4da 100644
--- a/src/Microsoft.TestPlatform.ObjectModel/PublicAPI/PublicAPI.Shipped.txt
+++ b/src/Microsoft.TestPlatform.ObjectModel/PublicAPI/PublicAPI.Shipped.txt
@@ -228,8 +228,8 @@ Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.ITestMessageEventHandler.
Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.ITestMessageEventHandler.HandleRawMessage(string rawMessage) -> void
Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.ITestPlatform
Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.ITestPlatform.ClearExtensions() -> void
-Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.ITestPlatform.CreateDiscoveryRequest(Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.IRequestData requestData, Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.DiscoveryCriteria discoveryCriteria, Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.TestPlatformOptions options) -> Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.IDiscoveryRequest
-Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.ITestPlatform.CreateTestRunRequest(Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.IRequestData requestData, Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.TestRunCriteria testRunCriteria, Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.TestPlatformOptions options) -> Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.ITestRunRequest
+Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.ITestPlatform.CreateDiscoveryRequest(Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.IRequestData requestData, Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.DiscoveryCriteria discoveryCriteria, Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.TestPlatformOptions options, System.Collections.Generic.Dictionary sourceToSourceDetailMap) -> Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.IDiscoveryRequest
+Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.ITestPlatform.CreateTestRunRequest(Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.IRequestData requestData, Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.TestRunCriteria testRunCriteria, Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.TestPlatformOptions options, System.Collections.Generic.Dictionary sourceToSourceDetailMap) -> Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.ITestRunRequest
Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.ITestPlatform.UpdateExtensions(System.Collections.Generic.IEnumerable pathToAdditionalExtensions, bool skipExtensionFilters) -> void
Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.ITestPlatformCapabilities
Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.ITestPlatformCapabilities.TestPlatformType.get -> Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.TestPlatformType
@@ -893,7 +893,7 @@ Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.DiscoveryCriteria.TestSes
Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.DiscoveryCriteria.TestSessionInfo.set -> void
Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.DiscoveryRequestPayload.TestSessionInfo.get -> Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.TestSessionInfo
Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.DiscoveryRequestPayload.TestSessionInfo.set -> void
-Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.ITestPlatform.StartTestSession(Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.IRequestData requestData, Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.StartTestSessionCriteria criteria, Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.ITestSessionEventsHandler eventsHandler) -> bool
+Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.ITestPlatform.StartTestSession(Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.IRequestData requestData, Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.StartTestSessionCriteria criteria, Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.ITestSessionEventsHandler eventsHandler, System.Collections.Generic.Dictionary sourceToSourceDetailMap) -> bool
Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.ITestSessionEventsHandler.HandleStartTestSessionComplete(Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.StartTestSessionCompleteEventArgs eventArgs) -> void
Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.ITestSessionEventsHandler.HandleStopTestSessionComplete(Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.StopTestSessionCompleteEventArgs eventArgs) -> void
Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.Payloads.StartTestSessionAckPayload.EventArgs.get -> Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.StartTestSessionCompleteEventArgs
@@ -928,3 +928,21 @@ Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.TestRunCompleteEventArgs.
Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.TestRunCompleteEventArgs.DiscoveredExtensions.set -> void
Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.DiscoveryCompleteEventArgs.IsAborted.set -> void
Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.DiscoveryCompleteEventArgs.TotalCount.set -> void
+Microsoft.VisualStudio.TestPlatform.ObjectModel.SourceDetail
+Microsoft.VisualStudio.TestPlatform.ObjectModel.SourceDetail.Architecture.get -> Microsoft.VisualStudio.TestPlatform.ObjectModel.Architecture
+Microsoft.VisualStudio.TestPlatform.ObjectModel.SourceDetail.Framework.get -> Microsoft.VisualStudio.TestPlatform.ObjectModel.Framework
+Microsoft.VisualStudio.TestPlatform.ObjectModel.SourceDetail.Source.get -> string
+Microsoft.VisualStudio.TestPlatform.ObjectModel.SourceDetail.SourceDetail() -> void
+Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.Interfaces.AttachDebuggerInfo
+Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.Interfaces.AttachDebuggerInfo.AttachDebuggerInfo() -> void
+Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.Interfaces.AttachDebuggerInfo.CancellationToken.get -> System.Threading.CancellationToken
+Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.Interfaces.AttachDebuggerInfo.CancellationToken.set -> void
+Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.Interfaces.AttachDebuggerInfo.ProcessId.get -> int
+Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.Interfaces.AttachDebuggerInfo.ProcessId.set -> void
+Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.Interfaces.AttachDebuggerInfo.TargetFramework.get -> Microsoft.VisualStudio.TestPlatform.ObjectModel.Framework
+Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.Interfaces.AttachDebuggerInfo.TargetFramework.set -> void
+Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.Interfaces.AttachDebuggerInfo.Version.get -> System.Version
+Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.Interfaces.AttachDebuggerInfo.Version.set -> void
+Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.Interfaces.ITestHostLauncher3
+Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.Interfaces.ITestHostLauncher3.AttachDebuggerToProcess(Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.Interfaces.AttachDebuggerInfo attachDebuggerInfo) -> bool
+
diff --git a/src/Microsoft.TestPlatform.ObjectModel/PublicAPI/net/PublicAPI.Unshipped.txt b/src/Microsoft.TestPlatform.ObjectModel/PublicAPI/net/PublicAPI.Unshipped.txt
index e02abfc9b0..5f282702bb 100644
--- a/src/Microsoft.TestPlatform.ObjectModel/PublicAPI/net/PublicAPI.Unshipped.txt
+++ b/src/Microsoft.TestPlatform.ObjectModel/PublicAPI/net/PublicAPI.Unshipped.txt
@@ -1 +1 @@
-
+
\ No newline at end of file
diff --git a/src/Microsoft.TestPlatform.ObjectModel/Resources/CommonResources.Designer.cs b/src/Microsoft.TestPlatform.ObjectModel/Resources/CommonResources.Designer.cs
index 3a37b375e5..696900893f 100644
--- a/src/Microsoft.TestPlatform.ObjectModel/Resources/CommonResources.Designer.cs
+++ b/src/Microsoft.TestPlatform.ObjectModel/Resources/CommonResources.Designer.cs
@@ -20,7 +20,7 @@ namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.Resources {
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
- [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")]
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
public class CommonResources {
@@ -71,7 +71,7 @@ public static string CannotBeNullOrEmpty {
}
///
- /// Looks up a localized string similar to Test run detected DLL(s) which were built for different framework and platform versions. Following DLL(s) do not match current settings, which are {0} framework and {1} platform.{2}Go to {3} for more details on managing these settings..
+ /// Looks up a localized string similar to Test run detected DLL(s) which would use different framework and platform versions. Following DLL(s) do not match current settings, which are {0} framework and {1} platform.{2}Go to {3} for more details on managing these settings..
///
public static string DisplayChosenSettings {
get {
@@ -98,7 +98,7 @@ public static string NoMatchingSourcesFound {
}
///
- /// Looks up a localized string similar to {0} is built for Framework {1} and Platform {2}..
+ /// Looks up a localized string similar to {0} would use Framework {1} and Platform {2}..
///
public static string SourceIncompatible {
get {
diff --git a/src/Microsoft.TestPlatform.ObjectModel/Resources/CommonResources.resx b/src/Microsoft.TestPlatform.ObjectModel/Resources/CommonResources.resx
index be18466964..329f3050ef 100644
--- a/src/Microsoft.TestPlatform.ObjectModel/Resources/CommonResources.resx
+++ b/src/Microsoft.TestPlatform.ObjectModel/Resources/CommonResources.resx
@@ -121,7 +121,7 @@
The parameter cannot be null or empty.
- Test run detected DLL(s) which were built for different framework and platform versions. Following DLL(s) do not match current settings, which are {0} framework and {1} platform.{2}Go to {3} for more details on managing these settings.
+ Test run detected DLL(s) which would use different framework and platform versions. Following DLL(s) do not match current settings, which are {0} framework and {1} platform.{2}Go to {3} for more details on managing these settings.Settings file provided does not conform to required format.
@@ -130,6 +130,6 @@
None of the provided test containers match the Platform Architecture and .Net Framework settings for the test run. Platform: {0} .Net Framework: {1}. Go to http://go.microsoft.com/fwlink/?LinkID=330428 for more details on managing these settings.
- {0} is built for Framework {1} and Platform {2}.
+ {0} would use Framework {1} and Platform {2}.
-
\ No newline at end of file
+
diff --git a/src/Microsoft.TestPlatform.ObjectModel/Resources/xlf/CommonResources.cs.xlf b/src/Microsoft.TestPlatform.ObjectModel/Resources/xlf/CommonResources.cs.xlf
index 5e6f6bda56..a97e4c557b 100644
--- a/src/Microsoft.TestPlatform.ObjectModel/Resources/xlf/CommonResources.cs.xlf
+++ b/src/Microsoft.TestPlatform.ObjectModel/Resources/xlf/CommonResources.cs.xlf
@@ -8,9 +8,9 @@
Parametr nemůže být null nebo prázdný.
-
+
- Otestujte zjištěné knihovny DLL, které se vytvořily pro různé verze architektury a platformy. Následující knihovny DLL neodpovídají aktuálnímu nastavení, tedy architektuře {0} a platformě {1}.{2}Další podrobnosti o správě těchto nastavení najdete v {3}.
+ Otestujte zjištěné knihovny DLL, které se vytvořily pro různé verze architektury a platformy. Následující knihovny DLL neodpovídají aktuálnímu nastavení, tedy architektuře {0} a platformě {1}.{2}Další podrobnosti o správě těchto nastavení najdete v {3}.
@@ -23,8 +23,8 @@
-
- Produkt {0} je sestaven pro rozhraní {1} a platformu {2}.
+
+ Produkt {0} je sestaven pro rozhraní {1} a platformu {2}.