Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add option to not share .NET Framework testhosts #5018

Merged
merged 5 commits into from
May 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 22 additions & 2 deletions src/Microsoft.TestPlatform.Client/TestPlatform.cs
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ public IDiscoveryRequest CreateDiscoveryRequest(
loggerManager.Initialize(discoveryCriteria.RunSettings);

IProxyDiscoveryManager discoveryManager = _testEngine.GetDiscoveryManager(requestData, discoveryCriteria, sourceToSourceDetailMap, warningLogger);
discoveryManager.Initialize(options?.SkipDefaultAdapters ?? false);
discoveryManager.Initialize(GetSkipDefaultAdapters(options, discoveryCriteria.RunSettings));

return new DiscoveryRequest(requestData, discoveryCriteria, discoveryManager, loggerManager);
}
Expand All @@ -110,11 +110,31 @@ public ITestRunRequest CreateTestRunRequest(
loggerManager.Initialize(testRunCriteria.TestRunSettings);

IProxyExecutionManager executionManager = _testEngine.GetExecutionManager(requestData, testRunCriteria, sourceToSourceDetailMap, warningLogger);
executionManager.Initialize(options?.SkipDefaultAdapters ?? false);
executionManager.Initialize(GetSkipDefaultAdapters(options, testRunCriteria.TestRunSettings));

return new TestRunRequest(requestData, testRunCriteria, executionManager, loggerManager);
}

private static bool GetSkipDefaultAdapters(TestPlatformOptions? options, string? runSettings)
{
if (options?.SkipDefaultAdapters ?? false)
{
EqtTrace.Verbose($"TestPlatform.GetSkipDefaultAdapters: Skipping default adapters because of TestPlatform options SkipDefaultAdapters.");
return true;
}

RunConfiguration runConfiguration = XmlRunSettingsUtilities.GetRunConfigurationNode(runSettings);
var skipping = runConfiguration.SkipDefaultAdapters;
if (skipping)
{
EqtTrace.Verbose($"TestPlatform.GetSkipDefaultAdapters: Skipping default adapters because of RunConfiguration SkipDefaultAdapters.");
return true;
}

EqtTrace.Verbose($"TestPlatform.GetSkipDefaultAdapters: Not skipping default adapters SkipDefaultAdapters was false.");
return false;
}

/// <inheritdoc/>
public bool StartTestSession(
IRequestData requestData,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@ private FeatureFlag() { }
// Disable forwarding standard output of testhost.
public const string VSTEST_DISABLE_STANDARD_OUTPUT_FORWARDING = nameof(VSTEST_DISABLE_STANDARD_OUTPUT_FORWARDING);

// Disable not sharing .NET Framework testhosts. Which will return behavior to sharing testhosts when they are running .NET Framework dlls, and are not disabling appdomains or running in parallel.
public const string VSTEST_DISABLE_SHARING_NETFRAMEWORK_TESTHOST = nameof(VSTEST_DISABLE_SHARING_NETFRAMEWORK_TESTHOST);


[Obsolete("Only use this in tests.")]
internal static void Reset()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -974,3 +974,5 @@ virtual Microsoft.VisualStudio.TestPlatform.ObjectModel.ValidateValueCallback.In
Microsoft.VisualStudio.TestPlatform.ObjectModel.Architecture.RiscV64 = 8 -> Microsoft.VisualStudio.TestPlatform.ObjectModel.Architecture
Microsoft.VisualStudio.TestPlatform.ObjectModel.RunConfiguration.CaptureStandardOutput.get -> bool
Microsoft.VisualStudio.TestPlatform.ObjectModel.RunConfiguration.ForwardStandardOutput.get -> bool
Microsoft.VisualStudio.TestPlatform.ObjectModel.RunConfiguration.DisableSharedTestHost.get -> bool
Microsoft.VisualStudio.TestPlatform.ObjectModel.RunConfiguration.SkipDefaultAdapters.get -> bool
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ public RunConfiguration() : base(Constants.RunConfigurationSettingsName)
ExecutionThreadApartmentState = Constants.DefaultExecutionThreadApartmentState;
CaptureStandardOutput = !FeatureFlag.Instance.IsSet(FeatureFlag.VSTEST_DISABLE_STANDARD_OUTPUT_CAPTURING);
ForwardStandardOutput = !FeatureFlag.Instance.IsSet(FeatureFlag.VSTEST_DISABLE_STANDARD_OUTPUT_FORWARDING);
DisableSharedTestHost = FeatureFlag.Instance.IsSet(FeatureFlag.VSTEST_DISABLE_SHARING_NETFRAMEWORK_TESTHOST);
}

/// <summary>
Expand Down Expand Up @@ -455,6 +456,16 @@ public bool ResultsDirectorySet
/// </summary>
public bool ForwardStandardOutput { get; private set; }

/// <summary>
/// Disables sharing of .NET Framework testhosts.
/// </summary>
public bool DisableSharedTestHost { get; private set; }

/// <summary>
/// Skips passing VisualStudio built in adapters to the project.
/// </summary>
public bool SkipDefaultAdapters { get; private set; }

/// <inheritdoc/>
public override XmlElement ToXml()
{
Expand Down Expand Up @@ -578,6 +589,14 @@ public override XmlElement ToXml()
forwardStandardOutput.InnerXml = ForwardStandardOutput.ToString();
root.AppendChild(forwardStandardOutput);

XmlElement disableSharedTesthost = doc.CreateElement(nameof(DisableSharedTestHost));
disableSharedTesthost.InnerXml = DisableSharedTestHost.ToString();
root.AppendChild(disableSharedTesthost);

XmlElement skipDefaultAdapters = doc.CreateElement(nameof(SkipDefaultAdapters));
skipDefaultAdapters.InnerXml = SkipDefaultAdapters.ToString();
root.AppendChild(skipDefaultAdapters);

return root;
}

Expand Down Expand Up @@ -964,6 +983,38 @@ public static RunConfiguration FromXml(XmlReader reader)
runConfiguration.ForwardStandardOutput = bForwardStandardOutput;
break;

case nameof(DisableSharedTestHost):
{
XmlRunSettingsUtilities.ThrowOnHasAttributes(reader);
string element = reader.ReadElementContentAsString();

bool boolValue;
if (!bool.TryParse(element, out boolValue))
{
throw new SettingsException(string.Format(CultureInfo.CurrentCulture,
Resources.Resources.InvalidSettingsIncorrectValue, Constants.RunConfigurationSettingsName, boolValue, elementName));
}

runConfiguration.DisableSharedTestHost = boolValue;
break;
}

case nameof(SkipDefaultAdapters):
{
XmlRunSettingsUtilities.ThrowOnHasAttributes(reader);
string element = reader.ReadElementContentAsString();

bool boolValue;
if (!bool.TryParse(element, out boolValue))
{
throw new SettingsException(string.Format(CultureInfo.CurrentCulture,
Resources.Resources.InvalidSettingsIncorrectValue, Constants.RunConfigurationSettingsName, boolValue, elementName));
}

runConfiguration.SkipDefaultAdapters = boolValue;
break;
}

default:
// Ignore a runsettings element that we don't understand. It could occur in the case
// the test runner is of a newer version, but the test host is of an earlier version.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ public class DefaultTestHostManager : ITestRuntimeProvider2
private readonly IEnvironment _environment;
private readonly IDotnetHostHelper _dotnetHostHelper;
private readonly IEnvironmentVariableHelper _environmentVariableHelper;

private bool _disableAppDomain;
private Architecture _architecture;
private Framework? _targetFramework;
private ITestHostLauncher? _customTestHostLauncher;
Expand Down Expand Up @@ -199,10 +199,10 @@ public virtual TestProcessStartInfo GetTestHostProcessStartInfo(
testhostProcessPath = Path.Combine(currentWorkingDirectory, "..", testHostProcessName);
}

if (!Shared)
if (_disableAppDomain)
{
// Not sharing the host which means we need to pass the test assembly path as argument
// so that the test host can create an appdomain on startup (Main method) and set appbase
// When host appdomains are disabled (in that case host is not shared) we need to pass the test assembly path as argument
// so that the test host can create one appdomain on startup (Main method) and set appbase.
argumentsString += " --testsourcepath " + sources.FirstOrDefault()?.AddDoubleQuote();
}

Expand Down Expand Up @@ -379,7 +379,13 @@ public void Initialize(IMessageLogger? logger, string runsettingsXml)
_targetFramework = runConfiguration.TargetFramework;
_testHostProcess = null;

Shared = !runConfiguration.DisableAppDomain;
_disableAppDomain = runConfiguration.DisableAppDomain;
// If appdomains are disabled the host cannot be shared, because sharing means loading multiple assemblies
// into the same process, and without appdomains we cannot safely do that.
//
// The OPPOSITE is not true though, disabling testhost sharing does not mean that we should not load the
// dll into a separate appdomain in the host. It just means that we wish to run each dll in separate exe.
Shared = !_disableAppDomain && !runConfiguration.DisableSharedTestHost;
_hostExitedEventRaised = false;

IsInitialized = true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ public void NUnitRunAllTestExecution(RunnerInfo runnerInfo)

var arguments = PrepareArguments(
GetAssetFullPath("NUTestProject.dll"),
GetTestAdapterPath(UnitTestFramework.NUnit),
null, // GetTestAdapterPath(UnitTestFramework.NUnit),
string.Empty, FrameworkArgValue,
runnerInfo.InIsolationValue, TempDirectory.Path);
InvokeVsTest(arguments);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,20 @@ public void PropertiesShouldReturnEmptyDictionary()
Assert.AreEqual(0, _testHostManager.Properties.Count);
}

[TestMethod]
public void DefaultTestHostManagerShouldNotBeSharedWhenOptedIn()
{
_testHostManager.Initialize(_mockMessageLogger.Object, $"""
<?xml version="1.0" encoding="utf-8"?>
<RunSettings>
<RunConfiguration>
<DisableSharedTestHost>{true}</DisableSharedTestHost>
</RunConfiguration>
</RunSettings>
""");
Assert.IsFalse(_testHostManager.Shared);
}

[TestMethod]
public void DefaultTestHostManagerShouldBeShared()
{
Expand Down