-
Notifications
You must be signed in to change notification settings - Fork 323
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Run NetFramework tests inside vstest.console.exe process #1009
Changes from 6 commits
e98195a
ddb91be
bfb18d6
a881a1a
a2a391d
ede0e04
2f76f3c
d1237cd
172f9d0
39c40c2
ed5a9ca
4124fde
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
// Copyright (c) Microsoft Corporation. All rights reserved. | ||
// Licensed under the MIT license. See LICENSE file in the project root for full license information. | ||
|
||
namespace Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Client | ||
{ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Threading.Tasks; | ||
using Microsoft.VisualStudio.TestPlatform.ObjectModel; | ||
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; | ||
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine; | ||
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine.TesthostProtocol; | ||
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; | ||
|
||
class InProcessProxyDiscoveryManager : IProxyDiscoveryManager | ||
{ | ||
private ITestHostManagerFactory testHostManagerFactory; | ||
public bool IsInitialized { get; private set; } = false; | ||
|
||
/// <summary> | ||
/// Initializes a new instance of the <see cref="InProcessProxyDiscoveryManager"/> class. | ||
/// </summary> | ||
public InProcessProxyDiscoveryManager() : this(new TestHostManagerFactory()) | ||
{ | ||
} | ||
|
||
/// <summary> | ||
/// Initializes a new instance of the <see cref="InProcessProxyDiscoveryManager"/> class. | ||
/// </summary> | ||
/// <param name="testHostManagerFactory">Manager factory</param> | ||
internal InProcessProxyDiscoveryManager(ITestHostManagerFactory testHostManagerFactory) | ||
{ | ||
this.testHostManagerFactory = testHostManagerFactory; | ||
} | ||
|
||
/// <summary> | ||
/// Initializes test discovery. | ||
/// </summary> | ||
public void Initialize() | ||
{ | ||
if(!this.IsInitialized) | ||
{ | ||
var discoveryManager = this.testHostManagerFactory.GetDiscoveryManager(); | ||
|
||
// We don't need to pass list of extension as we are running inside vstest.console and | ||
// it will use TestPluginCache of vstest.console | ||
discoveryManager.Initialize(Enumerable.Empty<string>()); | ||
this.IsInitialized = true; | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// Discovers tests | ||
/// </summary> | ||
/// <param name="discoveryCriteria">Settings, parameters for the discovery request</param> | ||
/// <param name="eventHandler">EventHandler for handling discovery events from Engine</param> | ||
public void DiscoverTests(DiscoveryCriteria discoveryCriteria, ITestDiscoveryEventsHandler eventHandler) | ||
{ | ||
var discoveryManager = this.testHostManagerFactory.GetDiscoveryManager(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why calling There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. and we can call Initilize() instead of doing check below. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed |
||
|
||
Task.Run(() => | ||
{ | ||
try | ||
{ | ||
// Initialize extension before discovery if it’s not initialized | ||
if (!this.IsInitialized) | ||
{ | ||
discoveryManager.Initialize(Enumerable.Empty<string>()); | ||
} | ||
discoveryManager.DiscoverTests(discoveryCriteria, eventHandler); | ||
} | ||
catch (Exception exception) | ||
{ | ||
EqtTrace.Error("InProcessProxyDiscoveryManager.DiscoverTests: Failed to discover tests: {0}", exception); | ||
|
||
// Send a discovery complete to caller. | ||
eventHandler.HandleLogMessage(TestMessageLevel.Error, exception.Message); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Log exception stacktrace also on failure scenario. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. fixed |
||
eventHandler.HandleDiscoveryComplete(-1, new List<TestCase>(), true); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed |
||
} | ||
} | ||
); | ||
} | ||
|
||
/// <summary> | ||
/// Closes the current test operation. | ||
/// This function is of no use in this context as we are not creating any testhost | ||
/// </summary> | ||
public void Close() | ||
{ | ||
} | ||
|
||
/// <summary> | ||
/// Aborts the test operation. | ||
/// </summary> | ||
public void Abort() | ||
{ | ||
Task.Run(() => this.testHostManagerFactory.GetDiscoveryManager().Abort()); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we require any null check for this.testHostManagerFactory? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, we don't require. If its null, process should catch exception |
||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
// Copyright (c) Microsoft Corporation. All rights reserved. | ||
// Licensed under the MIT license. See LICENSE file in the project root for full license information. | ||
|
||
namespace Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Client | ||
{ | ||
using System; | ||
using System.Collections.ObjectModel; | ||
using System.Linq; | ||
using System.Threading.Tasks; | ||
using Microsoft.VisualStudio.TestPlatform.ObjectModel; | ||
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; | ||
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine; | ||
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine.ClientProtocol; | ||
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine.TesthostProtocol; | ||
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; | ||
|
||
class InProcessProxyExecutionManager : IProxyExecutionManager | ||
{ | ||
private ITestHostManagerFactory testHostManagerFactory; | ||
public bool IsInitialized { get; private set; } = false; | ||
|
||
/// <summary> | ||
/// Initializes a new instance of the <see cref="InProcessProxyexecutionManager"/> class. | ||
/// </summary> | ||
public InProcessProxyExecutionManager() : this(new TestHostManagerFactory()) | ||
{ | ||
} | ||
|
||
/// <summary> | ||
/// Initializes a new instance of the <see cref="InProcessProxyexecutionManager"/> class. | ||
/// </summary> | ||
/// <param name="testHostManagerFactory"> | ||
/// Manager factory | ||
/// </param> | ||
internal InProcessProxyExecutionManager(ITestHostManagerFactory testHostManagerFactory) | ||
{ | ||
this.testHostManagerFactory = testHostManagerFactory; | ||
} | ||
|
||
public void Initialize() | ||
{ | ||
if (!this.IsInitialized) | ||
{ | ||
var executionManager = this.testHostManagerFactory.GetExecutionManager(); | ||
|
||
// We don't need to pass list of extension as we are running inside vstest.console and | ||
// it will use TestPluginCache of vstest.console | ||
executionManager.Initialize(Enumerable.Empty<string>()); | ||
this.IsInitialized = true; | ||
} | ||
} | ||
|
||
public int StartTestRun(TestRunCriteria testRunCriteria, ITestRunEventsHandler eventHandler) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: doc. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. fixed |
||
{ | ||
try | ||
{ | ||
var executionManager = this.testHostManagerFactory.GetExecutionManager(); | ||
|
||
// Initialize extension before execution if not already initialized | ||
if (!this.IsInitialized) | ||
{ | ||
executionManager.Initialize(Enumerable.Empty<string>()); | ||
} | ||
|
||
var executionContext = new TestExecutionContext( | ||
testRunCriteria.FrequencyOfRunStatsChangeEvent, | ||
testRunCriteria.RunStatsChangeEventTimeout, | ||
inIsolation: false, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How is this used in TPv1? Are we missing scenario? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This info get passed to adapter through RunContext |
||
keepAlive: testRunCriteria.KeepAlive, | ||
isDataCollectionEnabled: false, | ||
areTestCaseLevelEventsRequired: false, | ||
hasTestRun: true, | ||
isDebug: (testRunCriteria.TestHostLauncher != null && testRunCriteria.TestHostLauncher.IsDebug), | ||
testCaseFilter: testRunCriteria.TestCaseFilter); | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should ask TestRuntimeProvider for extensions. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, we should. |
||
|
||
if (testRunCriteria.HasSpecificSources) | ||
{ | ||
// [TODO]: we need to revisit to second-last argument if we will enable datacollector. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: remove todo There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed |
||
Task.Run(() => executionManager.StartTestRun(testRunCriteria.AdapterSourceMap, testRunCriteria.TestRunSettings, executionContext, null, eventHandler)); | ||
} | ||
else | ||
{ | ||
// [TODO]: we need to revisit to second-last argument if we will enable datacollector. | ||
Task.Run(() => executionManager.StartTestRun(testRunCriteria.Tests, testRunCriteria.TestRunSettings, executionContext, null, eventHandler)); | ||
} | ||
} | ||
catch (Exception exception) | ||
{ | ||
EqtTrace.Error("InProcessProxyexecutionManager.StartTestRun: Failed to start test run: {0}", exception); | ||
|
||
// Send a discovery complete to caller. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: change comment to test execution complete There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed in next commit. |
||
eventHandler.HandleLogMessage(TestMessageLevel.Error, exception.Message); | ||
|
||
// Send a run complete to caller. | ||
var completeArgs = new TestRunCompleteEventArgs(null, false, true, exception, new Collection<AttachmentSet>(), TimeSpan.Zero); | ||
eventHandler.HandleTestRunComplete(completeArgs, null, null, null); | ||
} | ||
|
||
return 0; | ||
} | ||
|
||
/// <summary> | ||
/// Aborts the test operation. | ||
/// </summary> | ||
public void Abort() | ||
{ | ||
Task.Run(() => this.testHostManagerFactory.GetExecutionManager().Abort()); | ||
} | ||
|
||
/// <summary> | ||
/// Cancels the test run. | ||
/// </summary> | ||
public void Cancel() | ||
{ | ||
Task.Run(() => this.testHostManagerFactory.GetExecutionManager().Cancel()); | ||
} | ||
|
||
/// <summary> | ||
/// Closes the current test operation. | ||
/// This function is of no use in this context as we are not creating any testhost | ||
/// </summary> | ||
public void Close() | ||
{ | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -60,8 +60,11 @@ public void Initialize(IEnumerable<string> pathToAdditionalExtensions) | |
{ | ||
this.testPlatformEventSource.AdapterSearchStart(); | ||
|
||
// Start using these additional extensions | ||
TestPluginCache.Instance.DefaultExtensionPaths = pathToAdditionalExtensions; | ||
if (pathToAdditionalExtensions != null && pathToAdditionalExtensions.Count() > 0) | ||
{ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we use pathToAdditionalExtensions.Any() instead of pathToAdditionalExtensions.Count() > 0? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes we can use |
||
// Start using these additional extensions | ||
TestPluginCache.Instance.DefaultExtensionPaths = pathToAdditionalExtensions; | ||
} | ||
|
||
// Load and Initialize extensions. | ||
TestDiscoveryExtensionManager.LoadAndInitializeAllExtensions(false); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,6 +18,8 @@ namespace Microsoft.VisualStudio.TestPlatform.CrossPlatEngine | |
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine; | ||
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Host; | ||
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Utilities; | ||
using Microsoft.VisualStudio.TestPlatform.PlatformAbstractions; | ||
using Microsoft.VisualStudio.TestPlatform.PlatformAbstractions.Interfaces; | ||
|
||
/// <summary> | ||
/// Cross Platform test engine entry point for the client. | ||
|
@@ -28,16 +30,18 @@ public class TestEngine : ITestEngine | |
|
||
private readonly TestRuntimeProviderManager testHostProviderManager; | ||
private ITestExtensionManager testExtensionManager; | ||
private IProcessHelper processHelper; | ||
|
||
#endregion | ||
|
||
public TestEngine() : this(TestRuntimeProviderManager.Instance) | ||
public TestEngine() : this(TestRuntimeProviderManager.Instance, new ProcessHelper()) | ||
{ | ||
} | ||
|
||
protected TestEngine(TestRuntimeProviderManager testHostProviderManager) | ||
protected TestEngine(TestRuntimeProviderManager testHostProviderManager, IProcessHelper processHelper) | ||
{ | ||
this.testHostProviderManager = testHostProviderManager; | ||
this.processHelper = processHelper; | ||
} | ||
|
||
#region ITestEngine implementation | ||
|
@@ -59,14 +63,19 @@ public IProxyDiscoveryManager GetDiscoveryManager(ITestRuntimeProvider testHostM | |
{ | ||
var parallelLevel = this.VerifyParallelSettingAndCalculateParallelLevel(discoveryCriteria.Sources.Count(), discoveryCriteria.RunSettings); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this can be moved below There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed in next commit. |
||
|
||
if (this.ShouldRunInNoIsolation(discoveryCriteria.RunSettings)) | ||
{ | ||
return new InProcessProxyDiscoveryManager(); | ||
} | ||
|
||
Func<IProxyDiscoveryManager> proxyDiscoveryManagerCreator = delegate | ||
{ | ||
var hostManager = this.testHostProviderManager.GetTestHostManagerByRunConfiguration(discoveryCriteria.RunSettings); | ||
hostManager?.Initialize(TestSessionMessageLogger.Instance, discoveryCriteria.RunSettings); | ||
|
||
return new ProxyDiscoveryManager(new TestRequestSender(protocolConfig, hostManager.GetTestHostConnectionInfo()), hostManager); | ||
}; | ||
|
||
return !testHostManager.Shared ? new ParallelProxyDiscoveryManager(proxyDiscoveryManagerCreator, parallelLevel, sharedHosts: testHostManager.Shared) : proxyDiscoveryManagerCreator(); | ||
} | ||
|
||
|
@@ -86,6 +95,11 @@ public IProxyExecutionManager GetExecutionManager(ITestRuntimeProvider testHostM | |
|
||
var isDataCollectorEnabled = XmlRunSettingsUtilities.IsDataCollectionEnabled(testRunCriteria.TestRunSettings); | ||
|
||
if (parallelLevel <= 1 && !isDataCollectorEnabled && this.ShouldRunInNoIsolation(testRunCriteria.TestRunSettings)) | ||
{ | ||
return new InProcessProxyExecutionManager(); | ||
} | ||
|
||
// SetupChannel ProxyExecutionManager with data collection if data collectors are specififed in run settings. | ||
Func<IProxyExecutionManager> proxyExecutionManagerCreator = delegate | ||
{ | ||
|
@@ -103,7 +117,7 @@ public IProxyExecutionManager GetExecutionManager(ITestRuntimeProvider testHostM | |
return isDataCollectorEnabled ? new ProxyExecutionManagerWithDataCollection(requestSender, hostManager, new ProxyDataCollectionManager(testRunCriteria.TestRunSettings)) | ||
: new ProxyExecutionManager(requestSender, hostManager); | ||
}; | ||
|
||
// parallelLevel = 1 for desktop should go via else route. | ||
if (parallelLevel > 1 || !testHostManager.Shared) | ||
{ | ||
|
@@ -193,5 +207,30 @@ private int VerifyParallelSettingAndCalculateParallelLevel(int sourceCount, stri | |
|
||
return parallelLevelToUse; | ||
} | ||
|
||
private bool ShouldRunInNoIsolation(string runsettings) | ||
{ | ||
var currentProcessPath = this.processHelper.GetCurrentProcessFileName(); | ||
|
||
// If running with the dotnet executable, then dont run in NoIsolation | ||
if (currentProcessPath.EndsWith("dotnet", StringComparison.OrdinalIgnoreCase) | ||
|| currentProcessPath.EndsWith("dotnet.exe", StringComparison.OrdinalIgnoreCase)) | ||
{ | ||
return false; | ||
} | ||
|
||
var runConfiguration = XmlRunSettingsUtilities.GetRunConfigurationNode(runsettings); | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add a comment specifying when will we enable InProc run There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed |
||
if (runConfiguration.InProcess && | ||
!runConfiguration.DisableAppDomain && | ||
!runConfiguration.DesignMode && | ||
!(runConfiguration.TargetFrameworkVersion.Name.IndexOf("netstandard", StringComparison.OrdinalIgnoreCase) >= 0 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use |
||
|| runConfiguration.TargetFrameworkVersion.Name.IndexOf("netcoreapp", StringComparison.OrdinalIgnoreCase) >= 0)) | ||
{ | ||
return true ; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Log eqttrace message on successful/unsuccessful using of inproc. Give warning on using inproc option for not supported target frameworks. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed! |
||
} | ||
|
||
return false; | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
public or internal class?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
internal