Skip to content

Commit 5fc832f

Browse files
authored
Add option to not share .NET Framework testhosts (#5018)
* Add options to not share hosts and skip default adapters * Add tests * Fix conflated appdomaindisable and testhost sharing * Share by default but add opt-out * Use reactorable names
1 parent eefebeb commit 5fc832f

File tree

7 files changed

+105
-8
lines changed

7 files changed

+105
-8
lines changed

src/Microsoft.TestPlatform.Client/TestPlatform.cs

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ public IDiscoveryRequest CreateDiscoveryRequest(
8787
loggerManager.Initialize(discoveryCriteria.RunSettings);
8888

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

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

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

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

118+
private static bool GetSkipDefaultAdapters(TestPlatformOptions? options, string? runSettings)
119+
{
120+
if (options?.SkipDefaultAdapters ?? false)
121+
{
122+
EqtTrace.Verbose($"TestPlatform.GetSkipDefaultAdapters: Skipping default adapters because of TestPlatform options SkipDefaultAdapters.");
123+
return true;
124+
}
125+
126+
RunConfiguration runConfiguration = XmlRunSettingsUtilities.GetRunConfigurationNode(runSettings);
127+
var skipping = runConfiguration.SkipDefaultAdapters;
128+
if (skipping)
129+
{
130+
EqtTrace.Verbose($"TestPlatform.GetSkipDefaultAdapters: Skipping default adapters because of RunConfiguration SkipDefaultAdapters.");
131+
return true;
132+
}
133+
134+
EqtTrace.Verbose($"TestPlatform.GetSkipDefaultAdapters: Not skipping default adapters SkipDefaultAdapters was false.");
135+
return false;
136+
}
137+
118138
/// <inheritdoc/>
119139
public bool StartTestSession(
120140
IRequestData requestData,

src/Microsoft.TestPlatform.CoreUtilities/FeatureFlag/FeatureFlag.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,10 @@ private FeatureFlag() { }
6969
// Disable forwarding standard output of testhost.
7070
public const string VSTEST_DISABLE_STANDARD_OUTPUT_FORWARDING = nameof(VSTEST_DISABLE_STANDARD_OUTPUT_FORWARDING);
7171

72+
// 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.
73+
public const string VSTEST_DISABLE_SHARING_NETFRAMEWORK_TESTHOST = nameof(VSTEST_DISABLE_SHARING_NETFRAMEWORK_TESTHOST);
74+
75+
7276
[Obsolete("Only use this in tests.")]
7377
internal static void Reset()
7478
{

src/Microsoft.TestPlatform.ObjectModel/PublicAPI/PublicAPI.Shipped.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -974,3 +974,5 @@ virtual Microsoft.VisualStudio.TestPlatform.ObjectModel.ValidateValueCallback.In
974974
Microsoft.VisualStudio.TestPlatform.ObjectModel.Architecture.RiscV64 = 8 -> Microsoft.VisualStudio.TestPlatform.ObjectModel.Architecture
975975
Microsoft.VisualStudio.TestPlatform.ObjectModel.RunConfiguration.CaptureStandardOutput.get -> bool
976976
Microsoft.VisualStudio.TestPlatform.ObjectModel.RunConfiguration.ForwardStandardOutput.get -> bool
977+
Microsoft.VisualStudio.TestPlatform.ObjectModel.RunConfiguration.DisableSharedTestHost.get -> bool
978+
Microsoft.VisualStudio.TestPlatform.ObjectModel.RunConfiguration.SkipDefaultAdapters.get -> bool

src/Microsoft.TestPlatform.ObjectModel/RunSettings/RunConfiguration.cs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ public RunConfiguration() : base(Constants.RunConfigurationSettingsName)
9595
ExecutionThreadApartmentState = Constants.DefaultExecutionThreadApartmentState;
9696
CaptureStandardOutput = !FeatureFlag.Instance.IsSet(FeatureFlag.VSTEST_DISABLE_STANDARD_OUTPUT_CAPTURING);
9797
ForwardStandardOutput = !FeatureFlag.Instance.IsSet(FeatureFlag.VSTEST_DISABLE_STANDARD_OUTPUT_FORWARDING);
98+
DisableSharedTestHost = FeatureFlag.Instance.IsSet(FeatureFlag.VSTEST_DISABLE_SHARING_NETFRAMEWORK_TESTHOST);
9899
}
99100

100101
/// <summary>
@@ -455,6 +456,16 @@ public bool ResultsDirectorySet
455456
/// </summary>
456457
public bool ForwardStandardOutput { get; private set; }
457458

459+
/// <summary>
460+
/// Disables sharing of .NET Framework testhosts.
461+
/// </summary>
462+
public bool DisableSharedTestHost { get; private set; }
463+
464+
/// <summary>
465+
/// Skips passing VisualStudio built in adapters to the project.
466+
/// </summary>
467+
public bool SkipDefaultAdapters { get; private set; }
468+
458469
/// <inheritdoc/>
459470
public override XmlElement ToXml()
460471
{
@@ -578,6 +589,14 @@ public override XmlElement ToXml()
578589
forwardStandardOutput.InnerXml = ForwardStandardOutput.ToString();
579590
root.AppendChild(forwardStandardOutput);
580591

592+
XmlElement disableSharedTesthost = doc.CreateElement(nameof(DisableSharedTestHost));
593+
disableSharedTesthost.InnerXml = DisableSharedTestHost.ToString();
594+
root.AppendChild(disableSharedTesthost);
595+
596+
XmlElement skipDefaultAdapters = doc.CreateElement(nameof(SkipDefaultAdapters));
597+
skipDefaultAdapters.InnerXml = SkipDefaultAdapters.ToString();
598+
root.AppendChild(skipDefaultAdapters);
599+
581600
return root;
582601
}
583602

@@ -964,6 +983,38 @@ public static RunConfiguration FromXml(XmlReader reader)
964983
runConfiguration.ForwardStandardOutput = bForwardStandardOutput;
965984
break;
966985

986+
case nameof(DisableSharedTestHost):
987+
{
988+
XmlRunSettingsUtilities.ThrowOnHasAttributes(reader);
989+
string element = reader.ReadElementContentAsString();
990+
991+
bool boolValue;
992+
if (!bool.TryParse(element, out boolValue))
993+
{
994+
throw new SettingsException(string.Format(CultureInfo.CurrentCulture,
995+
Resources.Resources.InvalidSettingsIncorrectValue, Constants.RunConfigurationSettingsName, boolValue, elementName));
996+
}
997+
998+
runConfiguration.DisableSharedTestHost = boolValue;
999+
break;
1000+
}
1001+
1002+
case nameof(SkipDefaultAdapters):
1003+
{
1004+
XmlRunSettingsUtilities.ThrowOnHasAttributes(reader);
1005+
string element = reader.ReadElementContentAsString();
1006+
1007+
bool boolValue;
1008+
if (!bool.TryParse(element, out boolValue))
1009+
{
1010+
throw new SettingsException(string.Format(CultureInfo.CurrentCulture,
1011+
Resources.Resources.InvalidSettingsIncorrectValue, Constants.RunConfigurationSettingsName, boolValue, elementName));
1012+
}
1013+
1014+
runConfiguration.SkipDefaultAdapters = boolValue;
1015+
break;
1016+
}
1017+
9671018
default:
9681019
// Ignore a runsettings element that we don't understand. It could occur in the case
9691020
// the test runner is of a newer version, but the test host is of an earlier version.

src/Microsoft.TestPlatform.TestHostProvider/Hosting/DefaultTestHostManager.cs

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ public class DefaultTestHostManager : ITestRuntimeProvider2
5858
private readonly IEnvironment _environment;
5959
private readonly IDotnetHostHelper _dotnetHostHelper;
6060
private readonly IEnvironmentVariableHelper _environmentVariableHelper;
61-
61+
private bool _disableAppDomain;
6262
private Architecture _architecture;
6363
private Framework? _targetFramework;
6464
private ITestHostLauncher? _customTestHostLauncher;
@@ -199,10 +199,10 @@ public virtual TestProcessStartInfo GetTestHostProcessStartInfo(
199199
testhostProcessPath = Path.Combine(currentWorkingDirectory, "..", testHostProcessName);
200200
}
201201

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

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

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

385391
IsInitialized = true;

test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DifferentTestFrameworkSimpleTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ public void NUnitRunAllTestExecution(RunnerInfo runnerInfo)
163163

164164
var arguments = PrepareArguments(
165165
GetAssetFullPath("NUTestProject.dll"),
166-
GetTestAdapterPath(UnitTestFramework.NUnit),
166+
null, // GetTestAdapterPath(UnitTestFramework.NUnit),
167167
string.Empty, FrameworkArgValue,
168168
runnerInfo.InIsolationValue, TempDirectory.Path);
169169
InvokeVsTest(arguments);

test/Microsoft.TestPlatform.TestHostProvider.UnitTests/Hosting/DefaultTestHostManagerTests.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,20 @@ public void PropertiesShouldReturnEmptyDictionary()
394394
Assert.AreEqual(0, _testHostManager.Properties.Count);
395395
}
396396

397+
[TestMethod]
398+
public void DefaultTestHostManagerShouldNotBeSharedWhenOptedIn()
399+
{
400+
_testHostManager.Initialize(_mockMessageLogger.Object, $"""
401+
<?xml version="1.0" encoding="utf-8"?>
402+
<RunSettings>
403+
<RunConfiguration>
404+
<DisableSharedTestHost>{true}</DisableSharedTestHost>
405+
</RunConfiguration>
406+
</RunSettings>
407+
""");
408+
Assert.IsFalse(_testHostManager.Shared);
409+
}
410+
397411
[TestMethod]
398412
public void DefaultTestHostManagerShouldBeShared()
399413
{

0 commit comments

Comments
 (0)