Permalink
Browse files

Adding CancellationToken to Testhost launch async (#917)

* Adding CancellationToken to Testhost launch async

* LaunchTestHost need not always throw

* Adding test for Cancellation Token

* Adding testhostlaunch failure event in case operation was cancelled by user

* Clean up of ITestRuntimeProvider

* PR Comments

* Updated Comments

* PR comments
  • Loading branch information...
mayankbansal018 committed Jul 11, 2017
1 parent a3b7d2e commit dec379db1c5b7b35821448db060869af4db60482
@@ -7,6 +7,7 @@ namespace Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Client
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading;
using Microsoft.VisualStudio.TestPlatform.Common;
using Microsoft.VisualStudio.TestPlatform.Common.ExtensionFramework;
@@ -26,6 +27,7 @@ public class ProxyDiscoveryManager : ProxyOperationManager, IProxyDiscoveryManag
{
private readonly ITestRuntimeProvider testHostManager;
private IDataSerializer dataSerializer;
private CancellationTokenSource cancellationTokenSource;
#region Constructors
@@ -50,6 +52,7 @@ public ProxyDiscoveryManager(ITestRequestSender testRequestSender, ITestRuntimeP
/// <param name="testHostManager">
/// Test host Manager instance
/// </param>
/// <param name="dataSerializer"></param>
/// <param name="clientConnectionTimeout">
/// The client Connection Timeout
/// </param>
@@ -62,6 +65,7 @@ public ProxyDiscoveryManager(ITestRequestSender testRequestSender, ITestRuntimeP
{
this.dataSerializer = dataSerializer;
this.testHostManager = testHostManager;
this.cancellationTokenSource = new CancellationTokenSource();
}
#endregion
@@ -97,7 +101,7 @@ public void DiscoverTests(DiscoveryCriteria discoveryCriteria, ITestDiscoveryEve
this.InitializeExtensions(discoveryCriteria.Sources);
}
this.SetupChannel(discoveryCriteria.Sources);
this.SetupChannel(discoveryCriteria.Sources, this.cancellationTokenSource.Token);
this.RequestSender.DiscoverTests(discoveryCriteria, eventHandler);
}
catch (Exception exception)
@@ -146,7 +150,7 @@ private void InitializeExtensions(IEnumerable<string> sources)
// Only send this if needed.
if (platformExtensions.Any())
{
this.SetupChannel(sourceList);
this.SetupChannel(sourceList, this.cancellationTokenSource.Token);
this.RequestSender.InitializeDiscovery(platformExtensions, TestPluginCache.Instance.LoadOnlyWellKnownExtensions);
}
@@ -7,7 +7,10 @@ namespace Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Client
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading;
using Microsoft.VisualStudio.TestPlatform.Common;
using Microsoft.VisualStudio.TestPlatform.Common.ExtensionFramework;
using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities;
using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.Interfaces;
@@ -20,8 +23,6 @@ namespace Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Client
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging;
using Constants = Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Constants;
using System.Text.RegularExpressions;
using Microsoft.VisualStudio.TestPlatform.Common;
/// <summary>
/// Orchestrates test execution operations for the engine communicating with the client.
@@ -30,17 +31,17 @@ internal class ProxyExecutionManager : ProxyOperationManager, IProxyExecutionMan
{
private readonly ITestRuntimeProvider testHostManager;
private IDataSerializer dataSerializer;
private CancellationTokenSource cancellationTokenSource;
/// <inheritdoc/>
public bool IsInitialized { get; private set; } = false;
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="ProxyExecutionManager"/> class.
/// </summary>
/// <param name="testRequestSender">Test request sender instance.</param>
/// <param name="requestSender">Test request sender instance.</param>
/// <param name="testHostManager">Test host manager for this proxy.</param>
public ProxyExecutionManager(ITestRequestSender requestSender, ITestRuntimeProvider testHostManager) : this(requestSender, testHostManager, JsonDataSerializer.Instance, Constants.ClientConnectionTimeout)
{
@@ -52,12 +53,14 @@ public ProxyExecutionManager(ITestRequestSender requestSender, ITestRuntimeProvi
/// </summary>
/// <param name="requestSender">Request Sender instance</param>
/// <param name="testHostManager">Test host manager instance</param>
/// <param name="dataSerializer"></param>
/// <param name="clientConnectionTimeout">The client Connection Timeout</param>
internal ProxyExecutionManager(ITestRequestSender requestSender, ITestRuntimeProvider testHostManager, IDataSerializer dataSerializer, int clientConnectionTimeout)
: base(requestSender, testHostManager, clientConnectionTimeout)
{
this.testHostManager = testHostManager;
this.dataSerializer = dataSerializer;
this.cancellationTokenSource = new CancellationTokenSource();
}
#endregion
@@ -76,6 +79,7 @@ public virtual void Initialize()
EqtTrace.Verbose("ProxyExecutionManager: Test host is shared. SetupChannel it early.");
this.InitializeExtensions(Enumerable.Empty<string>());
}
this.IsInitialized = true;
}
@@ -105,7 +109,7 @@ public virtual int StartTestRun(TestRunCriteria testRunCriteria, ITestRunEventsH
this.InitializeExtensions(testSources);
}
this.SetupChannel(testRunCriteria.Sources);
this.SetupChannel(testRunCriteria.Sources, this.cancellationTokenSource.Token);
var executionContext = new TestExecutionContext(
testRunCriteria.FrequencyOfRunStatsChangeEvent,
@@ -191,7 +195,7 @@ private void InitializeExtensions(IEnumerable<string> sources)
// Only send this if needed.
if (platformExtensions.Any())
{
this.SetupChannel(sourceList);
this.SetupChannel(sourceList, this.cancellationTokenSource.Token);
this.RequestSender.InitializeExecution(platformExtensions, TestPluginCache.Instance.LoadOnlyWellKnownExtensions);
}
@@ -7,20 +7,19 @@ namespace Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Client
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestPlatform.CoreUtilities.Extensions;
using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.Interfaces;
using Microsoft.VisualStudio.TestPlatform.CoreUtilities.Extensions;
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Host;
using Microsoft.VisualStudio.TestPlatform.PlatformAbstractions;
using Microsoft.VisualStudio.TestPlatform.PlatformAbstractions.Interfaces;
using Microsoft.VisualStudio.TestPlatform.Utilities;
using CrossPlatEngineResources = Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Resources.Resources;
using System.Reflection;
using System.Linq;
/// <summary>
/// Base class for any operations that the client needs to drive through the engine.
@@ -33,9 +32,10 @@ public abstract class ProxyOperationManager
private readonly string versionCheckPropertyName = "IsVersionCheckRequired";
private readonly ManualResetEventSlim testHostExited = new ManualResetEventSlim(false);
private int testHostProcessId;
private bool initialized;
private string testHostProcessStdError;
private int testHostProcessId;
private bool testHostLaunched;
#region Constructors
@@ -52,6 +52,7 @@ protected ProxyOperationManager(ITestRequestSender requestSender, ITestRuntimePr
this.testHostManager = testHostManager;
this.processHelper = new ProcessHelper();
this.initialized = false;
this.testHostLaunched = false;
this.testHostProcessId = -1;
}
@@ -75,10 +76,12 @@ protected ProxyOperationManager(ITestRequestSender requestSender, ITestRuntimePr
/// Usually includes starting up the test host process.
/// </summary>
/// <param name="sources">List of test sources.</param>
public virtual void SetupChannel(IEnumerable<string> sources)
/// <param name="cancellationToken"></param>
public virtual void SetupChannel(IEnumerable<string> sources, CancellationToken cancellationToken)
{
var connTimeout = this.connectionTimeout;
// ToDo: change SetupChannel to return bool, which will specify that a successfull connection with testhost was established or not
if (!this.initialized)
{
this.testHostProcessStdError = string.Empty;
@@ -94,15 +97,13 @@ public virtual void SetupChannel(IEnumerable<string> sources)
// Get the test process start info
var testHostStartInfo = this.UpdateTestProcessStartInfo(this.testHostManager.GetTestHostProcessStartInfo(sources, null, connectionInfo));
// Launch the test host.
var hostLaunchedTask = this.testHostManager.LaunchTestHostAsync(testHostStartInfo);
try
{
this.testHostProcessId = hostLaunchedTask.Result;
// Launch the test host.
var hostLaunchedTask = this.testHostManager.LaunchTestHostAsync(testHostStartInfo, cancellationToken);
this.testHostLaunched = hostLaunchedTask.Result;
}
catch (OperationCanceledException ex)
catch (Exception ex)
{
throw new TestPlatformException(string.Format(CultureInfo.CurrentUICulture, ex.Message));
}
@@ -113,19 +114,19 @@ public virtual void SetupChannel(IEnumerable<string> sources)
{
ConsoleOutput.Instance.WriteLine(CrossPlatEngineResources.HostDebuggerWarning, OutputLevel.Warning);
ConsoleOutput.Instance.WriteLine(
string.Format("Process Id: {0}, Name: {1}", hostLaunchedTask.Result, this.processHelper.GetProcessName(hostLaunchedTask.Result)),
string.Format("Process Id: {0}, Name: {1}", this.testHostProcessId, this.processHelper.GetProcessName(this.testHostProcessId)),
OutputLevel.Information);
// Increase connection timeout when debugging is enabled.
connTimeout = 5 * this.connectionTimeout;
}
// Wait for a timeout for the client to connect.
if (!this.RequestSender.WaitForRequestHandlerConnection(connTimeout))
if (!this.testHostLaunched || !this.RequestSender.WaitForRequestHandlerConnection(connTimeout))
{
var errorMsg = CrossPlatEngineResources.InitializationFailed;
if (!string.IsNullOrWhiteSpace(this.testHostProcessStdError.ToString()))
if (!string.IsNullOrWhiteSpace(this.testHostProcessStdError))
{
// Testhost failed with error
errorMsg = string.Format(CrossPlatEngineResources.TestHostExitedWithError, this.testHostProcessStdError);
@@ -160,7 +161,15 @@ public virtual void Close()
{
try
{
this.RequestSender.EndSession();
// do not send message if host did not launch
if (this.testHostLaunched)
{
this.RequestSender.EndSession();
// We want to give test host a chance to safely close.
// The upper bound for wait should be 100ms.
this.testHostExited.Wait(100);
}
}
catch (Exception ex)
{
@@ -172,13 +181,10 @@ public virtual void Close()
{
this.initialized = false;
// We don't need to terminate if the test host has already terminated. The upper bound
// for wait should be 100ms.
if (this.testHostProcessId != -1 && !this.testHostExited.Wait(100))
{
EqtTrace.Warning("ProxyOperationManager: Timed out waiting for test host to exit. Will terminate process.");
this.testHostManager.TerminateAsync(this.testHostProcessId, CancellationToken.None).Wait();
}
EqtTrace.Warning("ProxyOperationManager: Timed out waiting for test host to exit. Will terminate process.");
// please clean up test host.
this.testHostManager.CleanTestHostAsync(CancellationToken.None).Wait();
this.testHostManager.HostExited -= this.TestHostManagerHostExited;
this.testHostManager.HostLaunched -= this.TestHostManagerHostLaunched;
@@ -205,7 +211,9 @@ protected virtual TestProcessStartInfo UpdateTestProcessStartInfo(TestProcessSta
protected string GetTimestampedLogFile(string logFile)
{
if (string.IsNullOrWhiteSpace(logFile))
{
return null;
}
return Path.ChangeExtension(
logFile,
@@ -219,6 +227,7 @@ protected string GetTimestampedLogFile(string logFile)
private void TestHostManagerHostLaunched(object sender, HostProviderEventArgs e)
{
EqtTrace.Verbose(e.Data);
this.testHostProcessId = e.ProcessId;
}
private void TestHostManagerHostExited(object sender, HostProviderEventArgs e)
@@ -9,6 +9,7 @@ namespace Microsoft.VisualStudio.TestPlatform.CrossPlatEngine
using Microsoft.VisualStudio.TestPlatform.Common.Hosting;
using Microsoft.VisualStudio.TestPlatform.Common.Logging;
using Microsoft.VisualStudio.TestPlatform.Common.Utilities;
using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities;
using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Client;
using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Client.Parallel;
using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.DataCollection;
@@ -17,7 +18,6 @@ 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.CommunicationUtilities;
/// <summary>
/// Cross Platform test engine entry point for the client.
@@ -5,12 +5,12 @@ namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.Host
{
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.Interfaces;
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging;
using System.Threading;
/// <summary>
/// Interface for TestRuntimeProvider which manages test host processes for test engine.
@@ -20,11 +20,12 @@ public interface ITestRuntimeProvider
#region events
/// <summary>
/// Raised when host is launched successfully
/// Consumed by TestPlatform to initialize connection b/w test host and testplatform
/// </summary>
event EventHandler<HostProviderEventArgs> HostLaunched;
/// <summary>
/// Raised when host is reports Error
/// Raised when host is cleaned up and removes all it's dependencies
/// </summary>
event EventHandler<HostProviderEventArgs> HostExited;
@@ -65,8 +66,9 @@ public interface ITestRuntimeProvider
/// Launches the test host for discovery/execution.
/// </summary>
/// <param name="testHostStartInfo">Start parameters for the test host.</param>
/// <returns>ProcessId of launched Process. 0 means not launched.</returns>
Task<int> LaunchTestHostAsync(TestProcessStartInfo testHostStartInfo);
/// <param name="cancellationToken"></param>
/// <returns>Returns whether the test host lauched successfully or not.</returns>
Task<bool> LaunchTestHostAsync(TestProcessStartInfo testHostStartInfo, CancellationToken cancellationToken);
/// <summary>
/// Gets the start parameters for the test host.
@@ -83,15 +85,20 @@ public interface ITestRuntimeProvider
/// mechanism. E.g. for .net core, extensions are discovered from the <c>testproject.deps.json</c> file.
/// </summary>
/// <param name="sources">List of test sources.</param>
/// <param name="extensions"></param>
/// <returns>List of paths to extension assemblies.</returns>
IEnumerable<string> GetTestPlatformExtensions(IEnumerable<string> sources, IEnumerable<string> extensions);
/// <summary>
/// Terminate the test host process.
/// Cleanup the test host process and it's dependencies.
/// </summary>
/// <param name="processId">Process identifier for the test host.</param>
/// <param name="cancellationToken">Cancellation token.</param>
Task TerminateAsync(int processId, CancellationToken cancellationToken);
/// <param name="cancellationToken">
/// Cancellation token.
/// </param>
/// <returns>
/// The <see cref="Task"/>.
/// </returns>
Task CleanTestHostAsync(CancellationToken cancellationToken);
}
/// <summary>
Oops, something went wrong.

0 comments on commit dec379d

Please sign in to comment.