1 // Copyright (c) Microsoft. All rights reserved. 2 // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 4 using System; 5 using System.Xml; 6 using System.Text; 7 using System.Collections; 8 using System.Collections.Generic; 9 using System.Text.RegularExpressions; 10 using System.Threading; 11 using Microsoft.Build.Framework; 12 using Microsoft.Build.BackEnd; 13 using Microsoft.Build.BackEnd.Logging; 14 using Microsoft.Build.Execution; 15 using Microsoft.Build.Shared; 16 using LegacyThreadingData = Microsoft.Build.Execution.LegacyThreadingData; 17 using Xunit; 18 19 namespace Microsoft.Build.UnitTests.BackEnd 20 { 21 public class NodeEndpointInProc_Tests 22 { EndpointOperationDelegate(NodeEndpointInProc endpoint)23 private delegate void EndpointOperationDelegate(NodeEndpointInProc endpoint); 24 25 private class MockHost : IBuildComponentHost, INodePacketFactory 26 { 27 private DataReceivedContext _dataReceivedContext; 28 private AutoResetEvent _dataReceivedEvent; 29 private BuildParameters _buildParameters; 30 31 /// <summary> 32 /// Retrieves the LegacyThreadingData associated with a particular component host 33 /// </summary> 34 private LegacyThreadingData _legacyThreadingData; 35 36 MockHost()37 public MockHost() 38 { 39 _buildParameters = new BuildParameters(); 40 _dataReceivedEvent = new AutoResetEvent(false); 41 _legacyThreadingData = new LegacyThreadingData(); 42 } 43 44 public ILoggingService LoggingService 45 { 46 get 47 { 48 throw new NotImplementedException(); 49 } 50 } 51 52 /// <summary> 53 /// Retrieves the LegacyThreadingData associated with a particular component host 54 /// </summary> 55 LegacyThreadingData IBuildComponentHost.LegacyThreadingData 56 { 57 get 58 { 59 return _legacyThreadingData; 60 } 61 } 62 63 public string Name 64 { 65 get 66 { 67 return "NodeEndpointInProc_Tests.MockHost"; 68 } 69 } 70 71 public BuildParameters BuildParameters 72 { 73 get 74 { 75 return _buildParameters; 76 } 77 } 78 79 #region IBuildComponentHost Members 80 GetComponent(BuildComponentType type)81 public IBuildComponent GetComponent(BuildComponentType type) 82 { 83 throw new NotImplementedException(); 84 } 85 RegisterFactory(BuildComponentType type, BuildComponentFactoryDelegate factory)86 public void RegisterFactory(BuildComponentType type, BuildComponentFactoryDelegate factory) 87 { 88 } 89 90 #endregion 91 92 #region INodePacketFactory Members 93 RegisterPacketHandler(NodePacketType packetType, NodePacketFactoryMethod factory, INodePacketHandler handler)94 public void RegisterPacketHandler(NodePacketType packetType, NodePacketFactoryMethod factory, INodePacketHandler handler) 95 { 96 throw new NotImplementedException(); 97 } 98 UnregisterPacketHandler(NodePacketType packetType)99 public void UnregisterPacketHandler(NodePacketType packetType) 100 { 101 throw new NotImplementedException(); 102 } 103 DeserializeAndRoutePacket(int nodeId, NodePacketType packetType, INodePacketTranslator translator)104 public void DeserializeAndRoutePacket(int nodeId, NodePacketType packetType, INodePacketTranslator translator) 105 { 106 throw new NotImplementedException(); 107 } 108 RoutePacket(int nodeId, INodePacket packet)109 public void RoutePacket(int nodeId, INodePacket packet) 110 { 111 _dataReceivedContext = new DataReceivedContext(Thread.CurrentThread, packet); 112 _dataReceivedEvent.Set(); 113 } 114 115 public DataReceivedContext DataReceivedContext 116 { 117 get { return _dataReceivedContext; } 118 } 119 120 public WaitHandle DataReceivedEvent 121 { 122 get { return _dataReceivedEvent; } 123 } 124 125 #endregion 126 } 127 private class TestPacket : INodePacket 128 { 129 #region INodePacket Members 130 131 public NodePacketType Type 132 { 133 get { throw new NotImplementedException(); } 134 } 135 Translate(INodePacketTranslator translator)136 public void Translate(INodePacketTranslator translator) 137 { 138 throw new NotImplementedException(); 139 } 140 141 #endregion 142 } 143 144 private struct LinkStatusContext 145 { 146 public readonly Thread thread; 147 public readonly LinkStatus status; 148 LinkStatusContextMicrosoft.Build.UnitTests.BackEnd.NodeEndpointInProc_Tests.LinkStatusContext149 public LinkStatusContext(Thread thread, LinkStatus status) 150 { 151 this.thread = thread; 152 this.status = status; 153 } 154 } 155 156 private struct DataReceivedContext 157 { 158 public readonly Thread thread; 159 public readonly INodePacket packet; 160 DataReceivedContextMicrosoft.Build.UnitTests.BackEnd.NodeEndpointInProc_Tests.DataReceivedContext161 public DataReceivedContext(Thread thread, INodePacket packet) 162 { 163 this.thread = thread; 164 this.packet = packet; 165 } 166 } 167 168 private Dictionary<INodeEndpoint, LinkStatusContext> _linkStatusTable; 169 private MockHost _host; 170 171 [Fact] ConstructionWithValidHost()172 public void ConstructionWithValidHost() 173 { 174 NodeEndpointInProc.EndpointPair endpoints = 175 NodeEndpointInProc.CreateInProcEndpoints( 176 NodeEndpointInProc.EndpointMode.Synchronous, _host); 177 Assert.NotNull(endpoints); 178 179 endpoints = 180 NodeEndpointInProc.CreateInProcEndpoints( 181 NodeEndpointInProc.EndpointMode.Asynchronous, _host); 182 Assert.NotNull(endpoints); 183 } 184 185 [Fact] ConstructionSynchronousWithInvalidHost()186 public void ConstructionSynchronousWithInvalidHost() 187 { 188 Assert.Throws<ArgumentNullException>(() => 189 { 190 NodeEndpointInProc.EndpointPair endpoints = 191 NodeEndpointInProc.CreateInProcEndpoints( 192 NodeEndpointInProc.EndpointMode.Synchronous, null); 193 } 194 ); 195 } 196 197 [Fact] ConstructionAsynchronousWithInvalidHost()198 public void ConstructionAsynchronousWithInvalidHost() 199 { 200 Assert.Throws<ArgumentNullException>(() => 201 { 202 NodeEndpointInProc.EndpointPair endpoints = 203 NodeEndpointInProc.CreateInProcEndpoints( 204 NodeEndpointInProc.EndpointMode.Asynchronous, null); 205 } 206 ); 207 } 208 /// <summary> 209 /// Verify that the links: 210 /// 1. are marked inactive 211 /// 2. and that attempting to send data while they are 212 /// inactive throws the expected exception. 213 /// </summary> 214 [Fact] InactiveLinkTestSynchronous()215 public void InactiveLinkTestSynchronous() 216 { 217 NodeEndpointInProc.EndpointPair endpoints = 218 NodeEndpointInProc.CreateInProcEndpoints( 219 NodeEndpointInProc.EndpointMode.Synchronous, _host); 220 221 CallOpOnEndpoints(endpoints, VerifyLinkInactive); 222 CallOpOnEndpoints(endpoints, VerifySendDataInvalidOperation); 223 CallOpOnEndpoints(endpoints, VerifyDisconnectInvalidOperation); 224 225 // The following should not throw 226 endpoints.ManagerEndpoint.Listen(_host); 227 endpoints.NodeEndpoint.Connect(_host); 228 } 229 230 /// <summary> 231 /// Verify that the links are marked inactive and that attempting to send data while they are 232 /// inactive throws the expected exception. 233 /// </summary> 234 [Fact] InactiveLinkTestAsynchronous()235 public void InactiveLinkTestAsynchronous() 236 { 237 NodeEndpointInProc.EndpointPair endpoints = 238 NodeEndpointInProc.CreateInProcEndpoints( 239 NodeEndpointInProc.EndpointMode.Asynchronous, _host); 240 241 CallOpOnEndpoints(endpoints, VerifyLinkInactive); 242 CallOpOnEndpoints(endpoints, VerifySendDataInvalidOperation); 243 CallOpOnEndpoints(endpoints, VerifyDisconnectInvalidOperation); 244 245 // The following should not throw 246 endpoints.ManagerEndpoint.Listen(_host); 247 endpoints.NodeEndpoint.Connect(_host); 248 249 endpoints.ManagerEndpoint.Disconnect(); 250 } 251 252 [Fact] ConnectionTestSynchronous()253 public void ConnectionTestSynchronous() 254 { 255 NodeEndpointInProc.EndpointPair endpoints = 256 NodeEndpointInProc.CreateInProcEndpoints( 257 NodeEndpointInProc.EndpointMode.Synchronous, _host); 258 259 endpoints.ManagerEndpoint.OnLinkStatusChanged += LinkStatusChanged; 260 endpoints.NodeEndpoint.OnLinkStatusChanged += LinkStatusChanged; 261 262 // Call listen. This shouldn't have any effect on the link statuses. 263 endpoints.ManagerEndpoint.Listen(_host); 264 CallOpOnEndpoints(endpoints, VerifyLinkInactive); 265 // No link status callback should have occurred. 266 Assert.False(_linkStatusTable.ContainsKey(endpoints.NodeEndpoint)); 267 Assert.False(_linkStatusTable.ContainsKey(endpoints.ManagerEndpoint)); 268 269 // Now call connect on the node side. This should activate the link on both ends. 270 endpoints.NodeEndpoint.Connect(_host); 271 CallOpOnEndpoints(endpoints, VerifyLinkActive); 272 273 // We should have received callbacks informing us of the link change. 274 Assert.Equal(_linkStatusTable[endpoints.NodeEndpoint].status, LinkStatus.Active); 275 Assert.Equal(_linkStatusTable[endpoints.ManagerEndpoint].status, LinkStatus.Active); 276 } 277 278 [Fact] DisconnectionTestSynchronous()279 public void DisconnectionTestSynchronous() 280 { 281 DisconnectionTestHelper(NodeEndpointInProc.EndpointMode.Synchronous); 282 } 283 284 [Fact] DisconnectionTestAsynchronous()285 public void DisconnectionTestAsynchronous() 286 { 287 DisconnectionTestHelper(NodeEndpointInProc.EndpointMode.Asynchronous); 288 } 289 290 291 [Fact] SynchronousData()292 public void SynchronousData() 293 { 294 // Create the endpoints 295 NodeEndpointInProc.EndpointPair endpoints = 296 NodeEndpointInProc.CreateInProcEndpoints( 297 NodeEndpointInProc.EndpointMode.Synchronous, _host); 298 299 // Connect the endpoints 300 endpoints.ManagerEndpoint.Listen(_host); 301 endpoints.NodeEndpoint.Connect(_host); 302 303 // Create our test packets 304 INodePacket managerPacket = new TestPacket(); 305 INodePacket nodePacket = new TestPacket(); 306 307 // Send data from the manager. We expect to receive it from the node endpoint, and it should 308 // be on the same thread. 309 endpoints.ManagerEndpoint.SendData(managerPacket); 310 Assert.Equal(_host.DataReceivedContext.packet, managerPacket); 311 Assert.Equal(_host.DataReceivedContext.thread.ManagedThreadId, Thread.CurrentThread.ManagedThreadId); 312 313 // Send data from the node. We expect to receive it from the manager endpoint, and it should 314 // be on the same thread. 315 endpoints.NodeEndpoint.SendData(nodePacket); 316 Assert.Equal(_host.DataReceivedContext.packet, nodePacket); 317 Assert.Equal(_host.DataReceivedContext.thread.ManagedThreadId, Thread.CurrentThread.ManagedThreadId); 318 } 319 320 [Fact] AsynchronousData()321 public void AsynchronousData() 322 { 323 // Create the endpoints 324 NodeEndpointInProc.EndpointPair endpoints = 325 NodeEndpointInProc.CreateInProcEndpoints( 326 NodeEndpointInProc.EndpointMode.Asynchronous, _host); 327 328 // Connect the endpoints 329 endpoints.ManagerEndpoint.Listen(_host); 330 endpoints.NodeEndpoint.Connect(_host); 331 332 // Create our test packets 333 INodePacket managerPacket = new TestPacket(); 334 INodePacket nodePacket = new TestPacket(); 335 336 // Send data from the manager. We expect to receive it from the node endpoint, and it should 337 // be on the same thread. 338 endpoints.ManagerEndpoint.SendData(managerPacket); 339 if (!_host.DataReceivedEvent.WaitOne(1000)) 340 { 341 Assert.True(false, "Data not received before timeout expired."); 342 } 343 Assert.Equal(_host.DataReceivedContext.packet, managerPacket); 344 Assert.NotEqual(_host.DataReceivedContext.thread.ManagedThreadId, Thread.CurrentThread.ManagedThreadId); 345 346 // Send data from the node. We expect to receive it from the manager endpoint, and it should 347 // be on the same thread. 348 endpoints.NodeEndpoint.SendData(nodePacket); 349 if (!_host.DataReceivedEvent.WaitOne(1000)) 350 { 351 Assert.True(false, "Data not received before timeout expired."); 352 } 353 Assert.Equal(_host.DataReceivedContext.packet, nodePacket); 354 Assert.NotEqual(_host.DataReceivedContext.thread.ManagedThreadId, Thread.CurrentThread.ManagedThreadId); 355 356 endpoints.ManagerEndpoint.Disconnect(); 357 } 358 NodeEndpointInProc_Tests()359 public NodeEndpointInProc_Tests() 360 { 361 _linkStatusTable = new Dictionary<INodeEndpoint, LinkStatusContext>(); 362 _host = new MockHost(); 363 } 364 CallOpOnEndpoints(NodeEndpointInProc.EndpointPair pair, EndpointOperationDelegate opDelegate)365 private void CallOpOnEndpoints(NodeEndpointInProc.EndpointPair pair, EndpointOperationDelegate opDelegate) 366 { 367 opDelegate(pair.NodeEndpoint); 368 opDelegate(pair.ManagerEndpoint); 369 } 370 VerifyLinkInactive(NodeEndpointInProc endpoint)371 private void VerifyLinkInactive(NodeEndpointInProc endpoint) 372 { 373 Assert.Equal(endpoint.LinkStatus, LinkStatus.Inactive); // "Expected LinkStatus to be Inactive" 374 } 375 VerifyLinkActive(NodeEndpointInProc endpoint)376 private void VerifyLinkActive(NodeEndpointInProc endpoint) 377 { 378 Assert.Equal(endpoint.LinkStatus, LinkStatus.Active); // "Expected LinkStatus to be Active" 379 } 380 VerifySendDataInvalidOperation(NodeEndpointInProc endpoint)381 private void VerifySendDataInvalidOperation(NodeEndpointInProc endpoint) 382 { 383 bool caught = false; 384 try 385 { 386 endpoint.SendData(new TestPacket()); 387 } 388 catch (InternalErrorException) 389 { 390 caught = true; 391 } 392 393 Assert.True(caught); // "Did not receive InternalErrorException." 394 } 395 VerifyDisconnectInvalidOperation(NodeEndpointInProc endpoint)396 private void VerifyDisconnectInvalidOperation(NodeEndpointInProc endpoint) 397 { 398 bool caught = false; 399 try 400 { 401 endpoint.Disconnect(); 402 } 403 catch (InternalErrorException) 404 { 405 caught = true; 406 } 407 Assert.True(caught); // "Did not receive InternalErrorException." 408 } 409 VerifyListenCallSuccess(NodeEndpointInProc endpoint)410 private void VerifyListenCallSuccess(NodeEndpointInProc endpoint) 411 { 412 endpoint.Listen(_host); 413 } 414 VerifyConnectCallSuccess(NodeEndpointInProc endpoint)415 private void VerifyConnectCallSuccess(NodeEndpointInProc endpoint) 416 { 417 endpoint.Connect(_host); 418 Assert.Equal(endpoint.LinkStatus, LinkStatus.Active); 419 } 420 DisconnectionTestHelper(NodeEndpointInProc.EndpointMode mode)421 private void DisconnectionTestHelper(NodeEndpointInProc.EndpointMode mode) 422 { 423 NodeEndpointInProc.EndpointPair endpoints = SetupConnection(mode); 424 endpoints.ManagerEndpoint.Disconnect(); 425 VerifyLinksAndCallbacksInactive(endpoints); 426 427 endpoints = SetupConnection(mode); 428 endpoints.NodeEndpoint.Disconnect(); 429 VerifyLinksAndCallbacksInactive(endpoints); 430 } 431 VerifyLinksAndCallbacksInactive(NodeEndpointInProc.EndpointPair endpoints)432 private void VerifyLinksAndCallbacksInactive(NodeEndpointInProc.EndpointPair endpoints) 433 { 434 CallOpOnEndpoints(endpoints, VerifyLinkInactive); 435 Assert.Equal(_linkStatusTable[endpoints.NodeEndpoint].status, LinkStatus.Inactive); 436 Assert.Equal(_linkStatusTable[endpoints.ManagerEndpoint].status, LinkStatus.Inactive); 437 } 438 SetupConnection(NodeEndpointInProc.EndpointMode mode)439 private NodeEndpointInProc.EndpointPair SetupConnection(NodeEndpointInProc.EndpointMode mode) 440 { 441 NodeEndpointInProc.EndpointPair endpoints = 442 NodeEndpointInProc.CreateInProcEndpoints(mode, _host); 443 444 endpoints.ManagerEndpoint.OnLinkStatusChanged += LinkStatusChanged; 445 endpoints.NodeEndpoint.OnLinkStatusChanged += LinkStatusChanged; 446 447 // Call listen. This shouldn't have any effect on the link statuses. 448 endpoints.ManagerEndpoint.Listen(_host); 449 endpoints.NodeEndpoint.Connect(_host); 450 451 return endpoints; 452 } 453 LinkStatusChanged(INodeEndpoint endpoint, LinkStatus status)454 private void LinkStatusChanged(INodeEndpoint endpoint, LinkStatus status) 455 { 456 lock (_linkStatusTable) 457 { 458 _linkStatusTable[endpoint] = new LinkStatusContext(Thread.CurrentThread, status); 459 } 460 } 461 } 462 } 463