diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/TestSession/ProxyTestSessionManager.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/TestSession/ProxyTestSessionManager.cs index 6cf006d847..2ed0607d9d 100644 --- a/src/Microsoft.TestPlatform.CrossPlatEngine/TestSession/ProxyTestSessionManager.cs +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/TestSession/ProxyTestSessionManager.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Globalization; +using System.Linq; using System.Threading.Tasks; using Microsoft.VisualStudio.TestPlatform.Common.Telemetry; @@ -12,6 +13,8 @@ using Microsoft.VisualStudio.TestPlatform.ObjectModel; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine; +using Microsoft.VisualStudio.TestPlatform.ObjectModel.Utilities; +using Microsoft.VisualStudio.TestPlatform.Utilities; using CrossPlatResources = Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Resources.Resources; @@ -42,6 +45,22 @@ private enum TestSessionState private readonly IList _proxyContainerList; private readonly IDictionary _proxyMap; private readonly Stopwatch _testSessionStopwatch; + private IDictionary _testSessionEnvironmentVariables = new Dictionary(); + + private IDictionary TestSessionEnvironmentVariables + { + get + { + if (_testSessionEnvironmentVariables.Count == 0) + { + _testSessionEnvironmentVariables = InferRunSettingsHelper.GetEnvironmentVariables( + _testSessionCriteria.RunSettings) + ?? _testSessionEnvironmentVariables; + } + + return _testSessionEnvironmentVariables; + } + } /// /// Initializes a new instance of the class. @@ -227,10 +246,7 @@ public virtual ProxyOperationManager DequeueProxy(string source, string runSetti // We must ensure the current run settings match the run settings from when the // testhost was started. If not, throw an exception to force the caller to create // its own proxy instead. - // - // TODO (copoiena): This run settings match is rudimentary. We should refine the - // match criteria in the future. - if (!_testSessionCriteria.RunSettings.Equals(runSettings)) + if (!CheckRunSettingsAreCompatible(runSettings)) { throw new InvalidOperationException( string.Format( @@ -382,6 +398,23 @@ private void DisposeProxies() _proxyMap.Clear(); } } + + private bool CheckRunSettingsAreCompatible(string requestRunSettings) + { + // Environment variable sets should be identical, otherwise it's not safe to reuse the + // already running testhosts. + var requestEnvironmentVariables = InferRunSettingsHelper.GetEnvironmentVariables(requestRunSettings); + if (requestEnvironmentVariables != null + && TestSessionEnvironmentVariables != null + && (requestEnvironmentVariables.Count != TestSessionEnvironmentVariables.Count + || requestEnvironmentVariables.Except(TestSessionEnvironmentVariables).Any())) + { + return false; + } + + // Data collection is not supported for test sessions yet. + return !XmlRunSettingsUtilities.IsDataCollectionEnabled(requestRunSettings); + } } /// diff --git a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/ProxyTestSessionManagerTests.cs b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/ProxyTestSessionManagerTests.cs index fc49143ab4..5a64e8f930 100644 --- a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/ProxyTestSessionManagerTests.cs +++ b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/ProxyTestSessionManagerTests.cs @@ -31,6 +31,42 @@ public class ProxyTestSessionManagerTests @"C:\temp\FakeTestAsset7.dll", @"C:\temp\FakeTestAsset8.dll", }; + private readonly string _runSettingsNoEnvVars = @" + + + + "; + private readonly string _runSettingsOneEnvVar = @" + + + + Test1 + + + "; + private readonly string _runSettingsTwoEnvVars = @" + + + + Test1 + Test2 + + + "; + private readonly string _runSettingsTwoEnvVarsAndDataCollectors = @" + + + + Test1 + Test2 + + + + + + + + "; private readonly string _fakeRunSettings = "FakeRunSettings"; private readonly ProtocolConfig _protocolConfig = new() { Version = 1 }; private Mock _mockEventsHandler; @@ -281,7 +317,7 @@ public void DequeueProxyShouldSucceedIfIdentificationCriteriaAreMet() mockProxyOperationManager.Setup(pom => pom.SetupChannel(It.IsAny>(), It.IsAny())) .Returns(true); - var testSessionCriteria = CreateTestSession(_fakeTestSources, _fakeRunSettings); + var testSessionCriteria = CreateTestSession(_fakeTestSources, _runSettingsNoEnvVars); var proxyManager = CreateProxy(testSessionCriteria, mockProxyOperationManager.Object); // StartSession should succeed. @@ -302,7 +338,7 @@ public void DequeueProxyShouldSucceedIfIdentificationCriteriaAreMet() // Second call to DequeueProxy fails because of runsettings mismatch. Assert.ThrowsException(() => proxyManager.DequeueProxy( testSessionCriteria.Sources[0], - "DummyRunSettings")); + _runSettingsOneEnvVar)); // Third call to DequeueProxy succeeds. Assert.AreEqual(proxyManager.DequeueProxy( @@ -316,6 +352,119 @@ public void DequeueProxyShouldSucceedIfIdentificationCriteriaAreMet() testSessionCriteria.RunSettings)); } + [TestMethod] + public void DequeueProxyTwoConsecutiveTimesWithEnqueueShouldBeSuccessful() + { + var mockProxyOperationManager = new Mock(null, null, null); + mockProxyOperationManager.Setup(pom => pom.SetupChannel(It.IsAny>(), It.IsAny())) + .Returns(true); + + var testSessionCriteria = CreateTestSession(_fakeTestSources, _runSettingsTwoEnvVars); + var proxyManager = CreateProxy(testSessionCriteria, mockProxyOperationManager.Object); + + // StartSession should succeed. + Assert.IsTrue(proxyManager.StartSession(_mockEventsHandler.Object, _mockRequestData.Object)); + mockProxyOperationManager.Verify(pom => pom.SetupChannel( + It.IsAny>(), + testSessionCriteria.RunSettings), + Times.Exactly(testSessionCriteria.Sources.Count)); + _mockEventsHandler.Verify(eh => eh.HandleStartTestSessionComplete( + It.IsAny()), + Times.Once); + + // Call to DequeueProxy succeeds. + Assert.AreEqual(proxyManager.DequeueProxy( + testSessionCriteria.Sources[0], + testSessionCriteria.RunSettings), + mockProxyOperationManager.Object); + + Assert.AreEqual(proxyManager.EnqueueProxy(mockProxyOperationManager.Object.Id), true); + + // Call to DequeueProxy succeeds when called with the same runsettings as before. + Assert.AreEqual(proxyManager.DequeueProxy( + testSessionCriteria.Sources[0], + testSessionCriteria.RunSettings), + mockProxyOperationManager.Object); + } + + [TestMethod] + public void DequeueProxyShouldFailIfRunSettingsMatchingFails() + { + var mockProxyOperationManager = new Mock(null, null, null); + mockProxyOperationManager.Setup(pom => pom.SetupChannel(It.IsAny>(), It.IsAny())) + .Returns(true); + + var testSessionCriteria = CreateTestSession(_fakeTestSources, _runSettingsOneEnvVar); + var proxyManager = CreateProxy(testSessionCriteria, mockProxyOperationManager.Object); + + // StartSession should succeed. + Assert.IsTrue(proxyManager.StartSession(_mockEventsHandler.Object, _mockRequestData.Object)); + mockProxyOperationManager.Verify(pom => pom.SetupChannel( + It.IsAny>(), + testSessionCriteria.RunSettings), + Times.Exactly(testSessionCriteria.Sources.Count)); + _mockEventsHandler.Verify(eh => eh.HandleStartTestSessionComplete( + It.IsAny()), + Times.Once); + + // This call to DequeueProxy fails because of runsettings mismatch. + Assert.ThrowsException(() => proxyManager.DequeueProxy( + testSessionCriteria.Sources[0], + _runSettingsTwoEnvVars)); + } + + [TestMethod] + public void DequeueProxyShouldFailIfRunSettingsMatchingFailsFor2EnvVariables() + { + var mockProxyOperationManager = new Mock(null, null, null); + mockProxyOperationManager.Setup(pom => pom.SetupChannel(It.IsAny>(), It.IsAny())) + .Returns(true); + + var testSessionCriteria = CreateTestSession(_fakeTestSources, _runSettingsTwoEnvVars); + var proxyManager = CreateProxy(testSessionCriteria, mockProxyOperationManager.Object); + + // StartSession should succeed. + Assert.IsTrue(proxyManager.StartSession(_mockEventsHandler.Object, _mockRequestData.Object)); + mockProxyOperationManager.Verify(pom => pom.SetupChannel( + It.IsAny>(), + testSessionCriteria.RunSettings), + Times.Exactly(testSessionCriteria.Sources.Count)); + _mockEventsHandler.Verify(eh => eh.HandleStartTestSessionComplete( + It.IsAny()), + Times.Once); + + // This call to DequeueProxy fails because of runsettings mismatch. + Assert.ThrowsException(() => proxyManager.DequeueProxy( + testSessionCriteria.Sources[0], + _runSettingsOneEnvVar)); + } + + [TestMethod] + public void DequeueProxyShouldFailIfRunSettingsMatchingFailsForDataCollectors() + { + var mockProxyOperationManager = new Mock(null, null, null); + mockProxyOperationManager.Setup(pom => pom.SetupChannel(It.IsAny>(), It.IsAny())) + .Returns(true); + + var testSessionCriteria = CreateTestSession(_fakeTestSources, _runSettingsTwoEnvVars); + var proxyManager = CreateProxy(testSessionCriteria, mockProxyOperationManager.Object); + + // StartSession should succeed. + Assert.IsTrue(proxyManager.StartSession(_mockEventsHandler.Object, _mockRequestData.Object)); + mockProxyOperationManager.Verify(pom => pom.SetupChannel( + It.IsAny>(), + testSessionCriteria.RunSettings), + Times.Exactly(testSessionCriteria.Sources.Count)); + _mockEventsHandler.Verify(eh => eh.HandleStartTestSessionComplete( + It.IsAny()), + Times.Once); + + // This call to DequeueProxy fails because of runsettings mismatch. + Assert.ThrowsException(() => proxyManager.DequeueProxy( + testSessionCriteria.Sources[0], + _runSettingsTwoEnvVarsAndDataCollectors)); + } + [TestMethod] public void EnqueueProxyShouldSucceedIfIdentificationCriteriaAreMet() { @@ -323,7 +472,7 @@ public void EnqueueProxyShouldSucceedIfIdentificationCriteriaAreMet() mockProxyOperationManager.Setup(pom => pom.SetupChannel(It.IsAny>(), It.IsAny())) .Returns(true); - var testSessionCriteria = CreateTestSession(_fakeTestSources, _fakeRunSettings); + var testSessionCriteria = CreateTestSession(_fakeTestSources, _runSettingsNoEnvVars); var proxyManager = CreateProxy(testSessionCriteria, mockProxyOperationManager.Object); // Validate sanity checks.