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