File: BackEnd\NodeEndpointInProc_Tests.cs
Web Access
Project: ..\..\..\src\Build.UnitTests\Microsoft.Build.Engine.UnitTests.csproj (Microsoft.Build.Engine.UnitTests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System;
using System.Collections.Generic;
using System.Threading;
using Microsoft.Build.BackEnd;
using Microsoft.Build.BackEnd.Logging;
using Microsoft.Build.Execution;
using Microsoft.Build.Framework;
using Microsoft.Build.Shared;
using LegacyThreadingData = Microsoft.Build.Execution.LegacyThreadingData;
using Xunit;
 
#nullable disable
 
namespace Microsoft.Build.UnitTests.BackEnd
{
    public class NodeEndpointInProc_Tests
    {
        private delegate void EndpointOperationDelegate(NodeEndpointInProc endpoint);
 
        private sealed class MockHost : IBuildComponentHost, INodePacketFactory
        {
            private DataReceivedContext _dataReceivedContext;
            private AutoResetEvent _dataReceivedEvent;
            private BuildParameters _buildParameters;
 
            /// <summary>
            /// Retrieves the LegacyThreadingData associated with a particular component host
            /// </summary>
            private LegacyThreadingData _legacyThreadingData;
 
 
            public MockHost()
            {
                _buildParameters = new BuildParameters();
                _dataReceivedEvent = new AutoResetEvent(false);
                _legacyThreadingData = new LegacyThreadingData();
            }
 
            public ILoggingService LoggingService
            {
                get
                {
                    throw new NotImplementedException();
                }
            }
 
            /// <summary>
            /// Retrieves the LegacyThreadingData associated with a particular component host
            /// </summary>
            LegacyThreadingData IBuildComponentHost.LegacyThreadingData
            {
                get
                {
                    return _legacyThreadingData;
                }
            }
 
            public string Name
            {
                get
                {
                    return "NodeEndpointInProc_Tests.MockHost";
                }
            }
 
            public BuildParameters BuildParameters
            {
                get
                {
                    return _buildParameters;
                }
            }
 
            #region IBuildComponentHost Members
 
            public IBuildComponent GetComponent(BuildComponentType type)
            {
                throw new NotImplementedException();
            }
 
            public TComponent GetComponent<TComponent>(BuildComponentType type) where TComponent : IBuildComponent => throw new NotImplementedException("Not expected to be used.");
 
            public void RegisterFactory(BuildComponentType type, BuildComponentFactoryDelegate factory)
            {
            }
 
            #endregion
 
            #region INodePacketFactory Members
 
            public void RegisterPacketHandler(NodePacketType packetType, NodePacketFactoryMethod factory, INodePacketHandler handler)
            {
                throw new NotImplementedException();
            }
 
            public void UnregisterPacketHandler(NodePacketType packetType)
            {
                throw new NotImplementedException();
            }
 
            public void DeserializeAndRoutePacket(int nodeId, NodePacketType packetType, ITranslator translator)
            {
                throw new NotImplementedException();
            }
 
            public void RoutePacket(int nodeId, INodePacket packet)
            {
                _dataReceivedContext = new DataReceivedContext(Thread.CurrentThread, packet);
                _dataReceivedEvent.Set();
            }
 
            public DataReceivedContext DataReceivedContext
            {
                get { return _dataReceivedContext; }
            }
 
            public WaitHandle DataReceivedEvent
            {
                get { return _dataReceivedEvent; }
            }
 
            #endregion
        }
        private sealed class TestPacket : INodePacket
        {
            #region INodePacket Members
 
            public NodePacketType Type
            {
                get { throw new NotImplementedException(); }
            }
 
            public void Translate(ITranslator translator)
            {
                throw new NotImplementedException();
            }
 
            #endregion
        }
 
        private struct LinkStatusContext
        {
            public readonly Thread thread;
            public readonly LinkStatus status;
 
            public LinkStatusContext(Thread thread, LinkStatus status)
            {
                this.thread = thread;
                this.status = status;
            }
        }
 
        private struct DataReceivedContext
        {
            public readonly Thread thread;
            public readonly INodePacket packet;
 
            public DataReceivedContext(Thread thread, INodePacket packet)
            {
                this.thread = thread;
                this.packet = packet;
            }
        }
 
        private Dictionary<INodeEndpoint, LinkStatusContext> _linkStatusTable;
        private MockHost _host;
 
        [Fact]
        public void ConstructionWithValidHost()
        {
            NodeEndpointInProc.EndpointPair endpoints =
                NodeEndpointInProc.CreateInProcEndpoints(
                    NodeEndpointInProc.EndpointMode.Synchronous, _host);
 
            endpoints =
                NodeEndpointInProc.CreateInProcEndpoints(
                    NodeEndpointInProc.EndpointMode.Asynchronous, _host);
        }
 
        [Fact]
        public void ConstructionSynchronousWithInvalidHost()
        {
            Assert.Throws<ArgumentNullException>(() =>
            {
                NodeEndpointInProc.CreateInProcEndpoints(
                    NodeEndpointInProc.EndpointMode.Synchronous, null);
            });
        }
 
        [Fact]
        public void ConstructionAsynchronousWithInvalidHost()
        {
            Assert.Throws<ArgumentNullException>(() =>
            {
                NodeEndpointInProc.CreateInProcEndpoints(
                    NodeEndpointInProc.EndpointMode.Asynchronous, null);
            });
        }
        /// <summary>
        /// Verify that the links:
        /// 1. are marked inactive
        /// 2. and that attempting to send data while they are
        /// inactive throws the expected exception.
        /// </summary>
        [Fact]
        public void InactiveLinkTestSynchronous()
        {
            NodeEndpointInProc.EndpointPair endpoints =
                NodeEndpointInProc.CreateInProcEndpoints(
                    NodeEndpointInProc.EndpointMode.Synchronous, _host);
 
            CallOpOnEndpoints(endpoints, VerifyLinkInactive);
            CallOpOnEndpoints(endpoints, VerifySendDataInvalidOperation);
            CallOpOnEndpoints(endpoints, VerifyDisconnectInvalidOperation);
 
            // The following should not throw
            endpoints.ManagerEndpoint.Listen(_host);
            endpoints.NodeEndpoint.Connect(_host);
        }
 
        /// <summary>
        /// Verify that the links are marked inactive and that attempting to send data while they are
        /// inactive throws the expected exception.
        /// </summary>
        [Fact]
        public void InactiveLinkTestAsynchronous()
        {
            NodeEndpointInProc.EndpointPair endpoints =
                NodeEndpointInProc.CreateInProcEndpoints(
                    NodeEndpointInProc.EndpointMode.Asynchronous, _host);
 
            CallOpOnEndpoints(endpoints, VerifyLinkInactive);
            CallOpOnEndpoints(endpoints, VerifySendDataInvalidOperation);
            CallOpOnEndpoints(endpoints, VerifyDisconnectInvalidOperation);
 
            // The following should not throw
            endpoints.ManagerEndpoint.Listen(_host);
            endpoints.NodeEndpoint.Connect(_host);
 
            endpoints.ManagerEndpoint.Disconnect();
        }
 
        [Fact]
        public void ConnectionTestSynchronous()
        {
            NodeEndpointInProc.EndpointPair endpoints =
                NodeEndpointInProc.CreateInProcEndpoints(
                    NodeEndpointInProc.EndpointMode.Synchronous, _host);
 
            endpoints.ManagerEndpoint.OnLinkStatusChanged += LinkStatusChanged;
            endpoints.NodeEndpoint.OnLinkStatusChanged += LinkStatusChanged;
 
            // Call listen.  This shouldn't have any effect on the link statuses.
            endpoints.ManagerEndpoint.Listen(_host);
            CallOpOnEndpoints(endpoints, VerifyLinkInactive);
            // No link status callback should have occurred.
            Assert.False(_linkStatusTable.ContainsKey(endpoints.NodeEndpoint));
            Assert.False(_linkStatusTable.ContainsKey(endpoints.ManagerEndpoint));
 
            // Now call connect on the node side.  This should activate the link on both ends.
            endpoints.NodeEndpoint.Connect(_host);
            CallOpOnEndpoints(endpoints, VerifyLinkActive);
 
            // We should have received callbacks informing us of the link change.
            Assert.Equal(LinkStatus.Active, _linkStatusTable[endpoints.NodeEndpoint].status);
            Assert.Equal(LinkStatus.Active, _linkStatusTable[endpoints.ManagerEndpoint].status);
        }
 
        [Fact]
        public void DisconnectionTestSynchronous()
        {
            DisconnectionTestHelper(NodeEndpointInProc.EndpointMode.Synchronous);
        }
 
        [Fact]
        public void DisconnectionTestAsynchronous()
        {
            DisconnectionTestHelper(NodeEndpointInProc.EndpointMode.Asynchronous);
        }
 
 
        [Fact]
        public void SynchronousData()
        {
            // Create the endpoints
            NodeEndpointInProc.EndpointPair endpoints =
                NodeEndpointInProc.CreateInProcEndpoints(
                    NodeEndpointInProc.EndpointMode.Synchronous, _host);
 
            // Connect the endpoints
            endpoints.ManagerEndpoint.Listen(_host);
            endpoints.NodeEndpoint.Connect(_host);
 
            // Create our test packets
            INodePacket managerPacket = new TestPacket();
            INodePacket nodePacket = new TestPacket();
 
            // Send data from the manager. We expect to receive it from the node endpoint, and it should
            // be on the same thread.
            endpoints.ManagerEndpoint.SendData(managerPacket);
            Assert.Equal(_host.DataReceivedContext.packet, managerPacket);
            Assert.Equal(_host.DataReceivedContext.thread.ManagedThreadId, Thread.CurrentThread.ManagedThreadId);
 
            // Send data from the node.  We expect to receive it from the manager endpoint, and it should
            // be on the same thread.
            endpoints.NodeEndpoint.SendData(nodePacket);
            Assert.Equal(_host.DataReceivedContext.packet, nodePacket);
            Assert.Equal(_host.DataReceivedContext.thread.ManagedThreadId, Thread.CurrentThread.ManagedThreadId);
        }
 
        [Fact]
        public void AsynchronousData()
        {
            // Create the endpoints
            NodeEndpointInProc.EndpointPair endpoints =
                NodeEndpointInProc.CreateInProcEndpoints(
                    NodeEndpointInProc.EndpointMode.Asynchronous, _host);
 
            // Connect the endpoints
            endpoints.ManagerEndpoint.Listen(_host);
            endpoints.NodeEndpoint.Connect(_host);
 
            // Create our test packets
            INodePacket managerPacket = new TestPacket();
            INodePacket nodePacket = new TestPacket();
 
            // Send data from the manager. We expect to receive it from the node endpoint, and it should
            // be on the same thread.
            endpoints.ManagerEndpoint.SendData(managerPacket);
            if (!_host.DataReceivedEvent.WaitOne(1000))
            {
                Assert.Fail("Data not received before timeout expired.");
            }
            Assert.Equal(_host.DataReceivedContext.packet, managerPacket);
            Assert.NotEqual(_host.DataReceivedContext.thread.ManagedThreadId, Thread.CurrentThread.ManagedThreadId);
 
            // Send data from the node.  We expect to receive it from the manager endpoint, and it should
            // be on the same thread.
            endpoints.NodeEndpoint.SendData(nodePacket);
            if (!_host.DataReceivedEvent.WaitOne(1000))
            {
                Assert.Fail("Data not received before timeout expired.");
            }
            Assert.Equal(_host.DataReceivedContext.packet, nodePacket);
            Assert.NotEqual(_host.DataReceivedContext.thread.ManagedThreadId, Thread.CurrentThread.ManagedThreadId);
 
            endpoints.ManagerEndpoint.Disconnect();
        }
 
        public NodeEndpointInProc_Tests()
        {
            _linkStatusTable = new Dictionary<INodeEndpoint, LinkStatusContext>();
            _host = new MockHost();
        }
 
        private void CallOpOnEndpoints(NodeEndpointInProc.EndpointPair pair, EndpointOperationDelegate opDelegate)
        {
            opDelegate(pair.NodeEndpoint);
            opDelegate(pair.ManagerEndpoint);
        }
 
        private void VerifyLinkInactive(NodeEndpointInProc endpoint)
        {
            Assert.Equal(LinkStatus.Inactive, endpoint.LinkStatus); // "Expected LinkStatus to be Inactive"
        }
 
        private void VerifyLinkActive(NodeEndpointInProc endpoint)
        {
            Assert.Equal(LinkStatus.Active, endpoint.LinkStatus); // "Expected LinkStatus to be Active"
        }
 
        private void VerifySendDataInvalidOperation(NodeEndpointInProc endpoint)
        {
            bool caught = false;
            try
            {
                endpoint.SendData(new TestPacket());
            }
            catch (InternalErrorException)
            {
                caught = true;
            }
 
            Assert.True(caught); // "Did not receive InternalErrorException."
        }
 
        private void VerifyDisconnectInvalidOperation(NodeEndpointInProc endpoint)
        {
            bool caught = false;
            try
            {
                endpoint.Disconnect();
            }
            catch (InternalErrorException)
            {
                caught = true;
            }
            Assert.True(caught); // "Did not receive InternalErrorException."
        }
 
        private void DisconnectionTestHelper(NodeEndpointInProc.EndpointMode mode)
        {
            NodeEndpointInProc.EndpointPair endpoints = SetupConnection(mode);
            endpoints.ManagerEndpoint.Disconnect();
            VerifyLinksAndCallbacksInactive(endpoints);
 
            endpoints = SetupConnection(mode);
            endpoints.NodeEndpoint.Disconnect();
            VerifyLinksAndCallbacksInactive(endpoints);
        }
 
        private void VerifyLinksAndCallbacksInactive(NodeEndpointInProc.EndpointPair endpoints)
        {
            CallOpOnEndpoints(endpoints, VerifyLinkInactive);
            Assert.Equal(LinkStatus.Inactive, _linkStatusTable[endpoints.NodeEndpoint].status);
            Assert.Equal(LinkStatus.Inactive, _linkStatusTable[endpoints.ManagerEndpoint].status);
        }
 
        private NodeEndpointInProc.EndpointPair SetupConnection(NodeEndpointInProc.EndpointMode mode)
        {
            NodeEndpointInProc.EndpointPair endpoints =
                NodeEndpointInProc.CreateInProcEndpoints(mode, _host);
 
            endpoints.ManagerEndpoint.OnLinkStatusChanged += LinkStatusChanged;
            endpoints.NodeEndpoint.OnLinkStatusChanged += LinkStatusChanged;
 
            // Call listen.  This shouldn't have any effect on the link statuses.
            endpoints.ManagerEndpoint.Listen(_host);
            endpoints.NodeEndpoint.Connect(_host);
 
            return endpoints;
        }
 
        private void LinkStatusChanged(INodeEndpoint endpoint, LinkStatus status)
        {
            lock (_linkStatusTable)
            {
                _linkStatusTable[endpoint] = new LinkStatusContext(Thread.CurrentThread, status);
            }
        }
    }
}