Skip to content

Commit

Permalink
Testhost sharing between discovery & execution (#2687)
Browse files Browse the repository at this point in the history
* Enabled test session info passing through TP
* Testhost sharing is working
* Fixed double channel close
* Added logic to avoid testhost cleanup
* Implemented a disposable TestSession
* Adjusted the number of testhosts to be spawned
* Added Obsolete attributes to mark the API isn't final
  • Loading branch information
cvpoienaru authored Nov 19, 2021
1 parent efb8422 commit 554938b
Show file tree
Hide file tree
Showing 60 changed files with 4,250 additions and 621 deletions.
35 changes: 7 additions & 28 deletions src/Microsoft.TestPlatform.Client/TestPlatform.cs
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ public ITestRunRequest CreateTestRunRequest(
}

/// <inheritdoc/>
public void StartTestSession(
public bool StartTestSession(
IRequestData requestData,
StartTestSessionCriteria testSessionCriteria,
ITestSessionEventsHandler eventsHandler)
Expand All @@ -170,44 +170,23 @@ public void StartTestSession(
this.AddExtensionAssemblies(testSessionCriteria.RunSettings);

var runConfiguration = XmlRunSettingsUtilities.GetRunConfigurationNode(testSessionCriteria.RunSettings);

// Update extension assemblies from source when design mode is false.
//
// TODO (copoiena): Is it possible for this code to run if we're not in design mode ?
// An use case for this would be when running tests with "dotnet test". Usually there's
// a build involved then.
if (!runConfiguration.DesignMode)
{
return;
}

// Initialize loggers.
var loggerManager = this.TestEngine.GetLoggerManager(requestData);
loggerManager.Initialize(testSessionCriteria.RunSettings);

var testHostManager = this.testHostProviderManager.GetTestHostManagerByRunConfiguration(testSessionCriteria.RunSettings);
ThrowExceptionIfTestHostManagerIsNull(testHostManager, testSessionCriteria.RunSettings);

testHostManager.Initialize(TestSessionMessageLogger.Instance, testSessionCriteria.RunSettings);

if (testSessionCriteria.TestHostLauncher != null)
{
testHostManager.SetCustomLauncher(testSessionCriteria.TestHostLauncher);
return false;
}

var testSessionManager = this.TestEngine.GetTestSessionManager(requestData, testHostManager, testSessionCriteria);
var testSessionManager = this.TestEngine.GetTestSessionManager(requestData, testSessionCriteria);
if (testSessionManager == null)
{
// The test session manager is null because the combination of runsettings and
// sources tells us we should run in-process (i.e. in vstest.console). Because
// of this no session will be created because there's no testhost to be launched.
// Expecting a subsequent call to execute tests with the same set of parameters.
eventsHandler.HandleStartTestSessionComplete(null);
return;
return false;
}

testSessionManager.Initialize(false);
testSessionManager.StartSession(testSessionCriteria, eventsHandler);
return testSessionManager.StartSession(eventsHandler);
}

/// <summary>
Expand All @@ -234,11 +213,11 @@ public void ClearExtensions()

private void ThrowExceptionIfTestHostManagerIsNull(
ITestRuntimeProvider testHostManager,
string settingXml)
string settingsXml)
{
if (testHostManager == null)
{
EqtTrace.Error("TestPlatform.CreateTestRunRequest: No suitable testHostProvider found for runsettings : {0}", settingXml);
EqtTrace.Error("TestPlatform.CreateTestRunRequest: No suitable testHostProvider found for runsettings : {0}", settingsXml);
throw new TestPlatformException(string.Format(CultureInfo.CurrentCulture, ClientResources.NoTestHostProviderFound));
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,28 +11,22 @@ namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine
/// </summary>
public interface IProxyTestSessionManager
{
/// <summary>
/// Initialize the proxy.
/// </summary>
///
/// <param name="skipDefaultAdapters">Skip default adapters flag.</param>
void Initialize(bool skipDefaultAdapters);

/// <summary>
/// Starts the test session based on the test session criteria.
/// </summary>
///
/// <param name="criteria">The test session criteria.</param>
/// <param name="eventsHandler">
/// Event handler for handling events fired during test session management operations.
/// </param>
void StartSession(
StartTestSessionCriteria criteria,
ITestSessionEventsHandler eventsHandler);
///
/// <returns>True if the operation succeeded, false otherwise.</returns>
bool StartSession(ITestSessionEventsHandler eventsHandler);

/// <summary>
/// Stops the test session.
/// </summary>
void StopSession();
///
/// <returns>True if the operation succeeded, false otherwise.</returns>
bool StopSession();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,15 +53,13 @@ IProxyExecutionManager GetExecutionManager(
/// <param name="requestData">
/// The request data for providing test session services and data.
/// </param>
/// <param name="testHostManager">Test host manager for the current test session.</param>
/// <param name="testSessionCriteria">
/// Test session criteria of the current test session.
/// </param>
///
/// <returns>An IProxyTestSessionManager object that can manage test sessions.</returns>
IProxyTestSessionManager GetTestSessionManager(
IRequestData requestData,
ITestRuntimeProvider testHostManager,
StartTestSessionCriteria testSessionCriteria);

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,8 @@ internal TestRequestSender(
{
}

public bool CloseConnectionOnOperationComplete { get; set; } = true;

/// <inheritdoc />
public int InitializeCommunication()
{
Expand Down Expand Up @@ -727,6 +729,16 @@ private bool IsOperationComplete()

private void SetOperationComplete()
{
// When sharing the testhost between discovery and execution we must keep the
// testhost alive after completing the operation it was spawned for. As such we
// suppress the test request sender channel close taking place here. This channel
// will be closed when the test session owner decides to dispose of the test session
// object.
if (!this.CloseConnectionOnOperationComplete)
{
return;
}

// Complete the currently ongoing operation (Discovery/Execution)
if (EqtTrace.IsVerboseEnabled)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ namespace Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Client
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;

using Microsoft.VisualStudio.TestPlatform.Common;
using Microsoft.VisualStudio.TestPlatform.Common.ExtensionFramework;
Expand All @@ -25,17 +26,43 @@ namespace Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Client
/// </summary>
public class ProxyDiscoveryManager : IProxyDiscoveryManager, IBaseProxy, ITestDiscoveryEventsHandler2
{
private ProxyOperationManager proxyOperationManager;
private readonly ITestRuntimeProvider testHostManager;
private IDataSerializer dataSerializer;
private bool isCommunicationEstablished;
private readonly TestSessionInfo testSessionInfo = null;
Func<string, ProxyDiscoveryManager, ProxyOperationManager> proxyOperationManagerCreator;

private ITestRuntimeProvider testHostManager;
private IRequestData requestData;

private readonly IFileHelper fileHelper;
private readonly IDataSerializer dataSerializer;
private bool isCommunicationEstablished;

private ProxyOperationManager proxyOperationManager = null;
private ITestDiscoveryEventsHandler2 baseTestDiscoveryEventsHandler;
private bool skipDefaultAdapters;
private readonly IFileHelper fileHelper;

#region Constructors

/// <summary>
/// Initializes a new instance of the <see cref="ProxyDiscoveryManager"/> class.
/// </summary>
///
/// <param name="testSessionInfo">The test session info.</param>
/// <param name="proxyOperationManagerCreator">The proxy operation manager creator.</param>
public ProxyDiscoveryManager(
TestSessionInfo testSessionInfo,
Func<string, ProxyDiscoveryManager, ProxyOperationManager> proxyOperationManagerCreator)
{
// Filling in test session info and proxy information.
this.testSessionInfo = testSessionInfo;
this.proxyOperationManagerCreator = proxyOperationManagerCreator;

this.requestData = null;
this.testHostManager = null;
this.dataSerializer = JsonDataSerializer.Instance;
this.fileHelper = new FileHelper();
this.isCommunicationEstablished = false;
}

/// <summary>
/// Initializes a new instance of the <see cref="ProxyDiscoveryManager"/> class.
/// </summary>
Expand All @@ -55,9 +82,7 @@ public ProxyDiscoveryManager(
testHostManager,
JsonDataSerializer.Instance,
new FileHelper())
{
this.testHostManager = testHostManager;
}
{ }

/// <summary>
/// Initializes a new instance of the <see cref="ProxyDiscoveryManager"/> class.
Expand All @@ -81,11 +106,12 @@ internal ProxyDiscoveryManager(
IDataSerializer dataSerializer,
IFileHelper fileHelper)
{
this.dataSerializer = dataSerializer;
this.testHostManager = testHostManager;
this.isCommunicationEstablished = false;
this.requestData = requestData;
this.testHostManager = testHostManager;

this.dataSerializer = dataSerializer;
this.fileHelper = fileHelper;
this.isCommunicationEstablished = false;

// Create a new proxy operation manager.
this.proxyOperationManager = new ProxyOperationManager(requestData, requestSender, testHostManager, this);
Expand All @@ -104,6 +130,16 @@ public void Initialize(bool skipDefaultAdapters)
/// <inheritdoc/>
public void DiscoverTests(DiscoveryCriteria discoveryCriteria, ITestDiscoveryEventsHandler2 eventHandler)
{
if (this.proxyOperationManager == null)
{
this.proxyOperationManager = this.proxyOperationManagerCreator(
discoveryCriteria.Sources.First(),
this);

this.testHostManager = this.proxyOperationManager.TestHostManager;
this.requestData = this.proxyOperationManager.RequestData;
}

this.baseTestDiscoveryEventsHandler = eventHandler;
try
{
Expand Down Expand Up @@ -149,6 +185,12 @@ public void DiscoverTests(DiscoveryCriteria discoveryCriteria, ITestDiscoveryEve
/// <inheritdoc/>
public void Abort()
{
// Do nothing if the proxy is not initialized yet.
if (this.proxyOperationManager == null)
{
return;
}

// Cancel fast, try to stop testhost deployment/launch
this.proxyOperationManager.CancellationTokenSource.Cancel();
this.Close();
Expand All @@ -157,7 +199,25 @@ public void Abort()
/// <inheritdoc/>
public void Close()
{
this.proxyOperationManager.Close();
// Do nothing if the proxy is not initialized yet.
if (this.proxyOperationManager == null)
{
return;
}

// When no test session is being used we don't share the testhost
// between test discovery and test run. The testhost is closed upon
// successfully completing the operation it was spawned for.
//
// In contrast, the new workflow (using test sessions) means we should keep
// the testhost alive until explicitly closed by the test session owner.
if (this.testSessionInfo == null)
{
this.proxyOperationManager.Close();
return;
}

TestSessionPool.Instance.ReturnProxy(this.testSessionInfo, this.proxyOperationManager.Id);
}

/// <inheritdoc/>
Expand Down
Loading

0 comments on commit 554938b

Please sign in to comment.