diff --git a/src/Microsoft.TestPlatform.TestHostProvider/Hosting/TestHostManagerCallbacks.cs b/src/Microsoft.TestPlatform.TestHostProvider/Hosting/TestHostManagerCallbacks.cs index 8da89ae301..396c2d9afa 100644 --- a/src/Microsoft.TestPlatform.TestHostProvider/Hosting/TestHostManagerCallbacks.cs +++ b/src/Microsoft.TestPlatform.TestHostProvider/Hosting/TestHostManagerCallbacks.cs @@ -3,6 +3,7 @@ using System; using System.Diagnostics; +using System.Runtime.InteropServices; using System.Text; using Microsoft.VisualStudio.TestPlatform.CoreUtilities.Extensions; @@ -14,6 +15,8 @@ namespace Microsoft.TestPlatform.TestHostProvider.Hosting; internal class TestHostManagerCallbacks { + private const int E_HANDLE = unchecked((int)0x80070006); + public static void ErrorReceivedCallback(StringBuilder testHostProcessStdError, string? data) { // Log all standard error message because on too much data we ignore starting part. @@ -32,7 +35,15 @@ public static void ErrorReceivedCallback(StringBuilder testHostProcessStdError, EqtTrace.Verbose("TestHostProvider.ExitCallBack: Host exited starting callback."); var testHostProcessStdErrorStr = testHostProcessStdError.ToString(); - processHelper.TryGetExitCode(process, out int exitCode); + int exitCode = -1; + try + { + processHelper.TryGetExitCode(process, out exitCode); + } + catch (COMException ex) when (ex.HResult == E_HANDLE) + { + EqtTrace.Error("TestHostProvider.ExitCallBack: Invalid process handle we cannot get the error code, error {0}.", ex); + } int procId = -1; try diff --git a/test/Microsoft.TestPlatform.TestHostProvider.UnitTests/Hosting/TestHostManagerCallbacksTests.cs b/test/Microsoft.TestPlatform.TestHostProvider.UnitTests/Hosting/TestHostManagerCallbacksTests.cs index 27cced2518..73ba99c26a 100644 --- a/test/Microsoft.TestPlatform.TestHostProvider.UnitTests/Hosting/TestHostManagerCallbacksTests.cs +++ b/test/Microsoft.TestPlatform.TestHostProvider.UnitTests/Hosting/TestHostManagerCallbacksTests.cs @@ -2,9 +2,13 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; using System.Text; using Microsoft.TestPlatform.TestHostProvider.Hosting; +using Microsoft.VisualStudio.TestPlatform.PlatformAbstractions; +using Microsoft.VisualStudio.TestPlatform.PlatformAbstractions.Interfaces; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace TestPlatform.TestHostProvider.Hosting.UnitTests; @@ -94,4 +98,103 @@ public void ErrorReceivedCallbackShouldAppendNewLineApproprioritlyWhenReachingMa Assert.AreEqual("1234" + Environment.NewLine.Substring(0, 1), _testHostProcessStdError.ToString()); } + + [TestMethod] + public void ErrorReceivedCallbackShouldNotCrashIfInvalidProcessHandle() + { + var handleProcessHelper = new InvalidHandleProcessHelper(); + bool onHostExitedCalled = false; + TestHostManagerCallbacks.ExitCallBack(handleProcessHelper, null, new StringBuilder(), + hostProviderEventArgs => + { + onHostExitedCalled = true; + Assert.AreEqual(-1, hostProviderEventArgs.ErrroCode); + }); + + Assert.IsTrue(handleProcessHelper.TryGetExitCodeCalled, "TryGetExitCodeCalled was not called"); + Assert.IsTrue(onHostExitedCalled, "onHostExited was not called"); + } + + private class InvalidHandleProcessHelper : IProcessHelper + { + public bool TryGetExitCodeCalled { get; private set; } + + public PlatformArchitecture GetCurrentProcessArchitecture() + { + throw new NotImplementedException(); + } + + public string? GetCurrentProcessFileName() + { + throw new NotImplementedException(); + } + + public int GetCurrentProcessId() + { + throw new NotImplementedException(); + } + + public string GetCurrentProcessLocation() + { + throw new NotImplementedException(); + } + + public string GetNativeDllDirectory() + { + throw new NotImplementedException(); + } + + public PlatformArchitecture GetProcessArchitecture(int processId) + { + throw new NotImplementedException(); + } + + public nint GetProcessHandle(int processId) + { + throw new NotImplementedException(); + } + + public int GetProcessId(object? process) + { + throw new NotImplementedException(); + } + + public string GetProcessName(int processId) + { + throw new NotImplementedException(); + } + + public string? GetTestEngineDirectory() + { + throw new NotImplementedException(); + } + + public object LaunchProcess(string processPath, string? arguments, string? workingDirectory, IDictionary? envVariables, Action? errorCallback, Action? exitCallBack, Action? outputCallBack) + { + throw new NotImplementedException(); + } + + public void SetExitCallback(int processId, Action? callbackAction) + { + throw new NotImplementedException(); + } + + public void TerminateProcess(object? process) + { + throw new NotImplementedException(); + } + + public bool TryGetExitCode(object? process, out int exitCode) + { + var err = new COMException("Invalid handle", unchecked((int)0x80070006)); + typeof(COMException).GetProperty("HResult")!.SetValue(err, unchecked((int)0x80070006)); + TryGetExitCodeCalled = true; + throw err; + } + + public void WaitForProcessExit(object? process) + { + throw new NotImplementedException(); + } + } }