diff --git a/src/Microsoft.TestPlatform.CommunicationUtilities/TestRequestSender.cs b/src/Microsoft.TestPlatform.CommunicationUtilities/TestRequestSender.cs index deae160a5c..9e68a20d34 100644 --- a/src/Microsoft.TestPlatform.CommunicationUtilities/TestRequestSender.cs +++ b/src/Microsoft.TestPlatform.CommunicationUtilities/TestRequestSender.cs @@ -313,8 +313,8 @@ public void OnClientProcessExit(string stdError) this.clientExitErrorMessage = stdError; this.clientExited.Set(); - // Note that we're not explicitly disconnecting the communication channel; wait for the - // channel to determine the disconnection on its own. + // Break communication loop. In somecases(E.g: When tests creates child processes to testhost) communication channel won't break if testhost exits. + this.communicationEndpoint.Stop(); } /// diff --git a/src/Microsoft.TestPlatform.PlatformAbstractions/Interfaces/System/IProcessHelper.cs b/src/Microsoft.TestPlatform.PlatformAbstractions/Interfaces/System/IProcessHelper.cs index eca17c7099..f8a9826629 100644 --- a/src/Microsoft.TestPlatform.PlatformAbstractions/Interfaces/System/IProcessHelper.cs +++ b/src/Microsoft.TestPlatform.PlatformAbstractions/Interfaces/System/IProcessHelper.cs @@ -90,7 +90,7 @@ public interface IProcessHelper /// /// Callback on process exit. /// - void SetExitCallback(int processId, Action callbackAction); + void SetExitCallback(int processId, Action callbackAction); /// /// Terminates a process. diff --git a/src/Microsoft.TestPlatform.PlatformAbstractions/common/System/ProcessHelper.cs b/src/Microsoft.TestPlatform.PlatformAbstractions/common/System/ProcessHelper.cs index ec924ed47b..98099294d7 100644 --- a/src/Microsoft.TestPlatform.PlatformAbstractions/common/System/ProcessHelper.cs +++ b/src/Microsoft.TestPlatform.PlatformAbstractions/common/System/ProcessHelper.cs @@ -51,10 +51,10 @@ public object LaunchProcess(string processPath, string arguments, string working process.Exited += (sender, args) => { // Call WaitForExit without again to ensure all streams are flushed, - // Add timeout to avoid indefinite waiting on child process exist. var p = sender as Process; try { + // Add timeout to avoid indefinite waiting on child process exit. p.WaitForExit(500); } catch (InvalidOperationException) @@ -131,15 +131,20 @@ public bool TryGetExitCode(object process, out int exitCode) } /// - public void SetExitCallback(int processId, Action callbackAction) + public void SetExitCallback(int processId, Action callbackAction) { - var process = Process.GetProcessById(processId); - - process.EnableRaisingEvents = true; - process.Exited += (sender, args) => + try + { + var process = Process.GetProcessById(processId); + process.EnableRaisingEvents = true; + process.Exited += (sender, args) => callbackAction?.Invoke(sender); + } + catch (ArgumentException) { - callbackAction.Invoke(); - }; + // Process.GetProcessById() throws ArgumentException if process is not running(identifier might be expired). + // Invoke callback immediately. + callbackAction?.Invoke(null); + } } /// diff --git a/src/Microsoft.TestPlatform.PlatformAbstractions/netstandard1.0/System/ProcessHelper.cs b/src/Microsoft.TestPlatform.PlatformAbstractions/netstandard1.0/System/ProcessHelper.cs index 7cd2ca470c..7390a926a1 100644 --- a/src/Microsoft.TestPlatform.PlatformAbstractions/netstandard1.0/System/ProcessHelper.cs +++ b/src/Microsoft.TestPlatform.PlatformAbstractions/netstandard1.0/System/ProcessHelper.cs @@ -62,7 +62,7 @@ public bool TryGetExitCode(object process, out int exitCode) } /// - public void SetExitCallback(int parentProcessId, Action callbackAction) + public void SetExitCallback(int parentProcessId, Action callbackAction) { throw new NotImplementedException(); } diff --git a/src/Microsoft.TestPlatform.PlatformAbstractions/uap10.0/System/ProcessHelper.cs b/src/Microsoft.TestPlatform.PlatformAbstractions/uap10.0/System/ProcessHelper.cs index 69525fa74a..c3b915397b 100644 --- a/src/Microsoft.TestPlatform.PlatformAbstractions/uap10.0/System/ProcessHelper.cs +++ b/src/Microsoft.TestPlatform.PlatformAbstractions/uap10.0/System/ProcessHelper.cs @@ -62,7 +62,7 @@ public bool TryGetExitCode(object process, out int exitCode) } /// - public void SetExitCallback(int parentProcessId, Action callbackAction) + public void SetExitCallback(int parentProcessId, Action callbackAction) { } diff --git a/src/Microsoft.TestPlatform.TestHostProvider/Hosting/DefaultTestHostManager.cs b/src/Microsoft.TestPlatform.TestHostProvider/Hosting/DefaultTestHostManager.cs index 8c25a98106..d758197d69 100644 --- a/src/Microsoft.TestPlatform.TestHostProvider/Hosting/DefaultTestHostManager.cs +++ b/src/Microsoft.TestPlatform.TestHostProvider/Hosting/DefaultTestHostManager.cs @@ -385,6 +385,7 @@ private bool LaunchHost(TestProcessStartInfo testHostStartInfo, CancellationToke { int processId = this.customTestHostLauncher.LaunchTestHost(testHostStartInfo); this.testHostProcess = Process.GetProcessById(processId); + this.processHelper.SetExitCallback(processId, this.ExitCallBack); } } catch (OperationCanceledException ex) diff --git a/src/Microsoft.TestPlatform.TestHostProvider/Hosting/DotnetTestHostManager.cs b/src/Microsoft.TestPlatform.TestHostProvider/Hosting/DotnetTestHostManager.cs index f7ff9a232c..77c10fabc6 100644 --- a/src/Microsoft.TestPlatform.TestHostProvider/Hosting/DotnetTestHostManager.cs +++ b/src/Microsoft.TestPlatform.TestHostProvider/Hosting/DotnetTestHostManager.cs @@ -340,6 +340,7 @@ private bool LaunchHost(TestProcessStartInfo testHostStartInfo, CancellationToke { var processId = this.testHostLauncher.LaunchTestHost(testHostStartInfo); this.testHostProcess = Process.GetProcessById(processId); + this.processHelper.SetExitCallback(processId, this.ExitCallBack); } } catch (OperationCanceledException ex) diff --git a/src/datacollector/Program.cs b/src/datacollector/Program.cs index ce3fe24ea1..dd281f8376 100644 --- a/src/datacollector/Program.cs +++ b/src/datacollector/Program.cs @@ -77,7 +77,7 @@ private static void Run(string[] args) var processHelper = new ProcessHelper(); processHelper.SetExitCallback( parentProcessId, - () => + (obj) => { EqtTrace.Info("DataCollector: ParentProcess '{0}' Exited.", parentProcessId); Environment.Exit(1); diff --git a/src/testhost.x86/DefaultEngineInvoker.cs b/src/testhost.x86/DefaultEngineInvoker.cs index 3ceb07f7ab..49aaad36d0 100644 --- a/src/testhost.x86/DefaultEngineInvoker.cs +++ b/src/testhost.x86/DefaultEngineInvoker.cs @@ -90,7 +90,7 @@ public void Invoke(IDictionary argsDictionary) var processHelper = new ProcessHelper(); processHelper.SetExitCallback( parentProcessId, - () => + (obj) => { EqtTrace.Info("DefaultEngineInvoker: ParentProcess '{0}' Exited.", parentProcessId); new PlatformEnvironment().Exit(1); diff --git a/src/vstest.console/Processors/PortArgumentProcessor.cs b/src/vstest.console/Processors/PortArgumentProcessor.cs index 790eff89c3..0e6b42af3f 100644 --- a/src/vstest.console/Processors/PortArgumentProcessor.cs +++ b/src/vstest.console/Processors/PortArgumentProcessor.cs @@ -203,7 +203,7 @@ private static IDesignModeClient InitializeDesignMode(int parentProcessId, IProc { if (parentProcessId > 0) { - processHelper.SetExitCallback(parentProcessId, () => + processHelper.SetExitCallback(parentProcessId, (obj) => { EqtTrace.Info($"PortArgumentProcessor: parent process:{parentProcessId} exited."); DesignModeClient.Instance?.HandleParentProcessExit(); diff --git a/test/Microsoft.TestPlatform.TestHostProvider.UnitTests/Hosting/DefaultTestHostManagerTests.cs b/test/Microsoft.TestPlatform.TestHostProvider.UnitTests/Hosting/DefaultTestHostManagerTests.cs index 82c4a7b1e3..23da236bcf 100644 --- a/test/Microsoft.TestPlatform.TestHostProvider.UnitTests/Hosting/DefaultTestHostManagerTests.cs +++ b/test/Microsoft.TestPlatform.TestHostProvider.UnitTests/Hosting/DefaultTestHostManagerTests.cs @@ -380,6 +380,18 @@ public void LaunchTestHostShouldUseCustomHostIfSet() Assert.AreEqual(currentProcess.Id, this.testHostId); } + [TestMethod] + public void LaunchTestHostShouldSetExitCallbackInCaseCustomHost() + { + var mockCustomLauncher = new Mock(); + this.testHostManager.SetCustomLauncher(mockCustomLauncher.Object); + var currentProcess = Process.GetCurrentProcess(); + mockCustomLauncher.Setup(mc => mc.LaunchTestHost(It.IsAny())).Returns(currentProcess.Id); + this.testHostManager.LaunchTestHostAsync(this.startInfo, CancellationToken.None).Wait(); + + this.mockProcessHelper.Verify(ph => ph.SetExitCallback(currentProcess.Id, It.IsAny>())); + } + [TestMethod] public void GetTestSourcesShouldReturnAppropriateSourceIfAppxRecipeIsProvided() { diff --git a/test/Microsoft.TestPlatform.TestHostProvider.UnitTests/Hosting/DotnetTestHostManagerTests.cs b/test/Microsoft.TestPlatform.TestHostProvider.UnitTests/Hosting/DotnetTestHostManagerTests.cs index de3e373bf3..4bf97e46c9 100644 --- a/test/Microsoft.TestPlatform.TestHostProvider.UnitTests/Hosting/DotnetTestHostManagerTests.cs +++ b/test/Microsoft.TestPlatform.TestHostProvider.UnitTests/Hosting/DotnetTestHostManagerTests.cs @@ -369,6 +369,20 @@ public async Task LaunchTestHostShouldLaunchProcessWithConnectionInfo() this.mockTestHostLauncher.Verify(thl => thl.LaunchTestHost(It.Is(x => x.Arguments.Equals(expectedArgs))), Times.Once); } + [TestMethod] + public void LaunchTestHostShouldSetExitCallBackInCaseCustomHost() + { + var expectedProcessId = Process.GetCurrentProcess().Id; + this.mockTestHostLauncher.Setup(thl => thl.LaunchTestHost(It.IsAny())).Returns(expectedProcessId); + this.mockFileHelper.Setup(ph => ph.Exists("testhost.dll")).Returns(true); + + var startInfo = this.GetDefaultStartInfo(); + this.dotnetHostManager.SetCustomLauncher(this.mockTestHostLauncher.Object); + this.dotnetHostManager.LaunchTestHostAsync(startInfo, CancellationToken.None).Wait(); + + this.mockProcessHelper.Verify(ph => ph.SetExitCallback(expectedProcessId, It.IsAny>())); + } + [TestMethod] public void GetTestHostProcessStartInfoShouldIncludeTestHostPathFromSourceDirectoryIfDepsFileNotFound() { diff --git a/test/vstest.console.UnitTests/Processors/PortArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/PortArgumentProcessorTests.cs index 921687b64f..897ea3cb4e 100644 --- a/test/vstest.console.UnitTests/Processors/PortArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/PortArgumentProcessorTests.cs @@ -124,7 +124,7 @@ public void ExecutorInitializeShouldSetProcessExitCallback() this.executor.Initialize(port.ToString()); - this.mockProcessHelper.Verify(ph => ph.SetExitCallback(processId, It.IsAny()), Times.Once); + this.mockProcessHelper.Verify(ph => ph.SetExitCallback(processId, It.IsAny>()), Times.Once); } [TestMethod]