diff --git a/src/Microsoft.TestPlatform.CoreUtilities/Helpers/EnvironmentVariableHelper.cs b/src/Microsoft.TestPlatform.CoreUtilities/Helpers/EnvironmentVariableHelper.cs index 03f14207f3..bc249f5318 100644 --- a/src/Microsoft.TestPlatform.CoreUtilities/Helpers/EnvironmentVariableHelper.cs +++ b/src/Microsoft.TestPlatform.CoreUtilities/Helpers/EnvironmentVariableHelper.cs @@ -13,8 +13,15 @@ namespace Microsoft.VisualStudio.TestPlatform.CoreUtilities.Helpers; internal class EnvironmentVariableHelper : IEnvironmentVariableHelper { + /// public string GetEnvironmentVariable(string variable) => Environment.GetEnvironmentVariable(variable); + + /// + public TEnum GetEnvironmentVariableAsEnum(string variable, TEnum defaultValue = default) where TEnum : struct, Enum + => Environment.GetEnvironmentVariable(variable) is string value && !string.IsNullOrEmpty(value) + ? Enum.TryParse(value, out var enumValue) ? enumValue : defaultValue + : defaultValue; } #endif diff --git a/src/Microsoft.TestPlatform.CoreUtilities/Helpers/Interfaces/IEnvironmentVariableHelper.cs b/src/Microsoft.TestPlatform.CoreUtilities/Helpers/Interfaces/IEnvironmentVariableHelper.cs index c936835a6b..2d7cd021a8 100644 --- a/src/Microsoft.TestPlatform.CoreUtilities/Helpers/Interfaces/IEnvironmentVariableHelper.cs +++ b/src/Microsoft.TestPlatform.CoreUtilities/Helpers/Interfaces/IEnvironmentVariableHelper.cs @@ -3,9 +3,25 @@ #nullable disable +using System; + namespace Microsoft.VisualStudio.TestPlatform.Utilities.Helpers.Interfaces; internal interface IEnvironmentVariableHelper { + /// + /// Retrieves the value of an environment variable from the current process. + /// + /// The name of the environment variable. + /// The value of the environment variable specified by variable, or null if the environment variable is not found. string GetEnvironmentVariable(string variable); + + /// + /// Retrieves the value of an environment variable from the current process and converts it to the given type. + /// + /// The type used for conversion. + /// The name of the environment variable. + /// The default value to return if the environment variable is not found. + /// + TEnum GetEnvironmentVariableAsEnum(string variable, TEnum defaultValue = default) where TEnum : struct, Enum; } diff --git a/src/Microsoft.TestPlatform.TestHostProvider/Hosting/DefaultTestHostManager.cs b/src/Microsoft.TestPlatform.TestHostProvider/Hosting/DefaultTestHostManager.cs index 51e5359e8d..88a4d8b760 100644 --- a/src/Microsoft.TestPlatform.TestHostProvider/Hosting/DefaultTestHostManager.cs +++ b/src/Microsoft.TestPlatform.TestHostProvider/Hosting/DefaultTestHostManager.cs @@ -17,6 +17,7 @@ using Microsoft.TestPlatform.TestHostProvider.Hosting; using Microsoft.TestPlatform.TestHostProvider.Resources; using Microsoft.VisualStudio.TestPlatform.CoreUtilities.Extensions; +using Microsoft.VisualStudio.TestPlatform.CoreUtilities.Helpers; using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Helpers; using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Helpers.Interfaces; using Microsoft.VisualStudio.TestPlatform.DesktopTestHostRuntimeProvider; @@ -57,6 +58,7 @@ public class DefaultTestHostManager : ITestRuntimeProvider2 private readonly IFileHelper _fileHelper; private readonly IEnvironment _environment; private readonly IDotnetHostHelper _dotnetHostHelper; + private readonly IEnvironmentVariableHelper _environmentVariableHelper; private ITestHostLauncher _customTestHostLauncher; private Process _testHostProcess; @@ -68,7 +70,12 @@ public class DefaultTestHostManager : ITestRuntimeProvider2 /// Initializes a new instance of the class. /// public DefaultTestHostManager() - : this(new ProcessHelper(), new FileHelper(), new PlatformEnvironment(), new DotnetHostHelper()) + : this( + new ProcessHelper(), + new FileHelper(), + new DotnetHostHelper(), + new PlatformEnvironment(), + new EnvironmentVariableHelper()) { } @@ -79,12 +86,18 @@ public DefaultTestHostManager() /// File helper instance. /// Instance of platform environment. /// Instance of dotnet host helper. - internal DefaultTestHostManager(IProcessHelper processHelper, IFileHelper fileHelper, IEnvironment environment, IDotnetHostHelper dotnetHostHelper) + internal DefaultTestHostManager( + IProcessHelper processHelper, + IFileHelper fileHelper, + IDotnetHostHelper dotnetHostHelper, + IEnvironment environment, + IEnvironmentVariableHelper environmentVariableHelper) { _processHelper = processHelper; _fileHelper = fileHelper; - _environment = environment; _dotnetHostHelper = dotnetHostHelper; + _environment = environment; + _environmentVariableHelper = environmentVariableHelper; } /// @@ -469,8 +482,31 @@ private bool LaunchHost(TestProcessStartInfo testHostStartInfo, CancellationToke _processHelper.SetExitCallback(processId, ExitCallBack); } + if (_testHostProcess is null) + { + return false; + } + + SetProcessPriority(_testHostProcess, _environmentVariableHelper); OnHostLaunched(new HostProviderEventArgs("Test Runtime launched", 0, _testHostProcess.Id)); - return _testHostProcess != null; + + return true; + } + + internal static void SetProcessPriority(Process testHostProcess, IEnvironmentVariableHelper environmentVariableHelper) + { + ProcessPriorityClass testHostPriority = ProcessPriorityClass.BelowNormal; + try + { + testHostPriority = environmentVariableHelper.GetEnvironmentVariableAsEnum("VSTEST_HOST_INTERNAL_PRIORITY", testHostPriority); + testHostProcess.PriorityClass = testHostPriority; + EqtTrace.Verbose("Setting test host process priority to {0}", testHostProcess.PriorityClass); + } + // Setting the process Priority can fail with Win32Exception, NotSupportedException or InvalidOperationException. + catch (Exception ex) + { + EqtTrace.Error("Failed to set test host process priority to {0}. Exception: {1}", testHostPriority, ex); + } } private string GetUwpSources(string uwpSource) diff --git a/src/Microsoft.TestPlatform.TestHostProvider/Hosting/DotnetTestHostManager.cs b/src/Microsoft.TestPlatform.TestHostProvider/Hosting/DotnetTestHostManager.cs index 48f0cd64d8..34c4515d10 100644 --- a/src/Microsoft.TestPlatform.TestHostProvider/Hosting/DotnetTestHostManager.cs +++ b/src/Microsoft.TestPlatform.TestHostProvider/Hosting/DotnetTestHostManager.cs @@ -688,9 +688,14 @@ private bool LaunchHost(TestProcessStartInfo testHostStartInfo, CancellationToke _processHelper.SetExitCallback(processId, ExitCallBack); } - OnHostLaunched(new HostProviderEventArgs("Test Runtime launched", 0, _testHostProcess.Id)); + if (_testHostProcess is null) + { + return false; + } - return _testHostProcess != null; + DefaultTestHostManager.SetProcessPriority(_testHostProcess, _environmentVariableHelper); + OnHostLaunched(new HostProviderEventArgs("Test Runtime launched", 0, _testHostProcess.Id)); + return true; } private string GetTestHostPath(string runtimeConfigDevPath, string depsFilePath, string sourceDirectory) diff --git a/test/Microsoft.TestPlatform.TestHostProvider.UnitTests/Hosting/DefaultTestHostManagerTests.cs b/test/Microsoft.TestPlatform.TestHostProvider.UnitTests/Hosting/DefaultTestHostManagerTests.cs index fed025f0c3..d66173a26c 100644 --- a/test/Microsoft.TestPlatform.TestHostProvider.UnitTests/Hosting/DefaultTestHostManagerTests.cs +++ b/test/Microsoft.TestPlatform.TestHostProvider.UnitTests/Hosting/DefaultTestHostManagerTests.cs @@ -12,6 +12,7 @@ using System.Threading.Tasks; using Microsoft.VisualStudio.TestPlatform.CoreUtilities.Extensions; +using Microsoft.VisualStudio.TestPlatform.CoreUtilities.Helpers; using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Helpers; using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Helpers.Interfaces; using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Hosting; @@ -39,6 +40,7 @@ public class DefaultTestHostManagerTests private readonly Mock _mockFileHelper; private readonly Mock _mockDotnetHostHelper; private readonly Mock _mockEnvironment; + private readonly Mock _mockEnvironmentVariable; private readonly DefaultTestHostManager _testHostManager; private TestableTestHostManager? _testableTestHostManager; @@ -53,10 +55,11 @@ public DefaultTestHostManagerTests() _mockProcessHelper.Setup(ph => ph.GetCurrentProcessFileName()).Returns("vstest.console.exe"); _mockDotnetHostHelper = new Mock(); _mockEnvironment = new Mock(); + _mockEnvironmentVariable = new Mock(); _mockMessageLogger = new Mock(); - _testHostManager = new DefaultTestHostManager(_mockProcessHelper.Object, _mockFileHelper.Object, _mockEnvironment.Object, _mockDotnetHostHelper.Object); + _testHostManager = new DefaultTestHostManager(_mockProcessHelper.Object, _mockFileHelper.Object, _mockDotnetHostHelper.Object, _mockEnvironment.Object, _mockEnvironmentVariable.Object); _testHostManager.Initialize(_mockMessageLogger.Object, $" {Architecture.X64} {Framework.DefaultFramework} {false} "); _startInfo = _testHostManager.GetTestHostProcessStartInfo(Enumerable.Empty(), null, default); } @@ -592,7 +595,7 @@ public TestableTestHostManager( IProcessHelper processHelper, bool shared, IMessageLogger logger) - : base(processHelper, new FileHelper(), new PlatformEnvironment(), new DotnetHostHelper()) + : base(processHelper, new FileHelper(), new DotnetHostHelper(), new PlatformEnvironment(), new EnvironmentVariableHelper()) { Initialize(logger, $" {architecture} {framework} {!shared} "); }