diff --git a/scripts/test.sh b/scripts/test.sh index e0f22848db..31f30794e9 100644 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -113,7 +113,7 @@ function invoke_test() local dotnet=$(_get_dotnet_path) local vstest=$TP_OUT_DIR/$TPB_Configuration/$TPB_TargetFrameworkCore/vstest.console.dll - find ./test -path $PROJECT_NAME_PATTERNS | xargs $dotnet $vstest --parallel + find ./test -path $PROJECT_NAME_PATTERNS | xargs --verbose $dotnet $vstest --parallel } # diff --git a/src/Microsoft.TestPlatform.Common/Interfaces/Communication/ICommunicationClient.cs b/src/Microsoft.TestPlatform.Common/Interfaces/Communication/ICommunicationClient.cs deleted file mode 100644 index 333769ecbb..0000000000 --- a/src/Microsoft.TestPlatform.Common/Interfaces/Communication/ICommunicationClient.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -namespace Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.Interfaces -{ - using System; - - /// - /// Client interface for inter-process communications in test platform. - /// - public interface ICommunicationClient - { - /// - /// Event raised when the client is connected to server. - /// - event EventHandler ServerConnected; - - /// - /// Event raise when connection with server is broken. - /// - event EventHandler ServerDisconnected; - - /// - /// Connect to the server specified in . - /// - /// Parameters to connect to server. - void Start(string connectionInfo); - - /// - /// Close the communication channel and stop the client. - /// - void Stop(); - } -} diff --git a/src/Microsoft.TestPlatform.Common/Interfaces/Communication/ICommunicationServer.cs b/src/Microsoft.TestPlatform.Common/Interfaces/Communication/ICommunicationServer.cs deleted file mode 100644 index b5101d4907..0000000000 --- a/src/Microsoft.TestPlatform.Common/Interfaces/Communication/ICommunicationServer.cs +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -namespace Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.Interfaces -{ - using System; - - /// - /// Server interface for inter-process communications in test platform. A server can serve only a single - /// client. - /// - public interface ICommunicationServer - { - /// - /// Event raised when a client is connected to the server. - /// - event EventHandler ClientConnected; - - /// - /// Event raised when a client is disconnected from the server. - /// - event EventHandler ClientDisconnected; - - /// - /// Starts a communication server. - /// - /// Connection parameters for a client to connect. - string Start(); - - /// - /// Stops the server and closes the underlying communication channel. - /// - void Stop(); - } -} diff --git a/src/Microsoft.TestPlatform.CommunicationUtilities/DataCollectionRequestHandler.cs b/src/Microsoft.TestPlatform.CommunicationUtilities/DataCollectionRequestHandler.cs index 5674482b9e..6d5908923d 100644 --- a/src/Microsoft.TestPlatform.CommunicationUtilities/DataCollectionRequestHandler.cs +++ b/src/Microsoft.TestPlatform.CommunicationUtilities/DataCollectionRequestHandler.cs @@ -121,11 +121,10 @@ public static DataCollectionRequestHandler Create( ICommunicationManager communicationManager, IMessageSink messageSink) { + ValidateArg.NotNull(communicationManager, nameof(communicationManager)); + ValidateArg.NotNull(messageSink, nameof(messageSink)); if (Instance == null) { - ValidateArg.NotNull(communicationManager, nameof(communicationManager)); - ValidateArg.NotNull(messageSink, nameof(messageSink)); - lock (SyncObject) { if (Instance == null) diff --git a/src/Microsoft.TestPlatform.CommunicationUtilities/Friends.cs b/src/Microsoft.TestPlatform.CommunicationUtilities/Friends.cs index f0e7dcadc7..0e55268a01 100644 --- a/src/Microsoft.TestPlatform.CommunicationUtilities/Friends.cs +++ b/src/Microsoft.TestPlatform.CommunicationUtilities/Friends.cs @@ -4,6 +4,7 @@ using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("Microsoft.TestPlatform.CommunicationUtilities.UnitTests, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] +[assembly: InternalsVisibleTo("Microsoft.TestPlatform.CommunicationUtilities.PlatformTests, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] [assembly: InternalsVisibleTo("vstest.console.UnitTests, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] [assembly: InternalsVisibleTo("Microsoft.TestPlatform.CrossPlatEngine, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] [assembly: InternalsVisibleTo("Microsoft.TestPlatform.CrossPlatEngine.UnitTests, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] diff --git a/src/Microsoft.TestPlatform.Common/Interfaces/Communication/CommunicationException.cs b/src/Microsoft.TestPlatform.CommunicationUtilities/Interfaces/Communication/CommunicationException.cs similarity index 100% rename from src/Microsoft.TestPlatform.Common/Interfaces/Communication/CommunicationException.cs rename to src/Microsoft.TestPlatform.CommunicationUtilities/Interfaces/Communication/CommunicationException.cs diff --git a/src/Microsoft.TestPlatform.Common/Interfaces/Communication/ConnectedEventArgs.cs b/src/Microsoft.TestPlatform.CommunicationUtilities/Interfaces/Communication/ConnectedEventArgs.cs similarity index 70% rename from src/Microsoft.TestPlatform.Common/Interfaces/Communication/ConnectedEventArgs.cs rename to src/Microsoft.TestPlatform.CommunicationUtilities/Interfaces/Communication/ConnectedEventArgs.cs index e7cd06042f..63747df454 100644 --- a/src/Microsoft.TestPlatform.Common/Interfaces/Communication/ConnectedEventArgs.cs +++ b/src/Microsoft.TestPlatform.CommunicationUtilities/Interfaces/Communication/ConnectedEventArgs.cs @@ -24,11 +24,28 @@ public ConnectedEventArgs() public ConnectedEventArgs(ICommunicationChannel channel) { this.Channel = channel; + this.Connected = true; + } + + public ConnectedEventArgs(Exception faultException) + { + this.Connected = false; + this.Fault = faultException; } /// /// Gets the communication channel based on this connection. /// public ICommunicationChannel Channel { get; private set; } + + /// + /// Gets true if it's connected. + /// + public bool Connected { get; private set; } + + /// + /// Gets the exception if it's not connected. + /// + public Exception Fault { get; private set; } } } diff --git a/src/Microsoft.TestPlatform.Common/Interfaces/Communication/DisconnectedEventArgs.cs b/src/Microsoft.TestPlatform.CommunicationUtilities/Interfaces/Communication/DisconnectedEventArgs.cs similarity index 100% rename from src/Microsoft.TestPlatform.Common/Interfaces/Communication/DisconnectedEventArgs.cs rename to src/Microsoft.TestPlatform.CommunicationUtilities/Interfaces/Communication/DisconnectedEventArgs.cs diff --git a/src/Microsoft.TestPlatform.Common/Interfaces/Communication/ICommunicationChannel.cs b/src/Microsoft.TestPlatform.CommunicationUtilities/Interfaces/Communication/ICommunicationChannel.cs similarity index 100% rename from src/Microsoft.TestPlatform.Common/Interfaces/Communication/ICommunicationChannel.cs rename to src/Microsoft.TestPlatform.CommunicationUtilities/Interfaces/Communication/ICommunicationChannel.cs diff --git a/src/Microsoft.TestPlatform.Common/Interfaces/Communication/MessageReceivedEventArgs.cs b/src/Microsoft.TestPlatform.CommunicationUtilities/Interfaces/Communication/MessageReceivedEventArgs.cs similarity index 100% rename from src/Microsoft.TestPlatform.Common/Interfaces/Communication/MessageReceivedEventArgs.cs rename to src/Microsoft.TestPlatform.CommunicationUtilities/Interfaces/Communication/MessageReceivedEventArgs.cs diff --git a/src/Microsoft.TestPlatform.CommunicationUtilities/Interfaces/ICommunicationEndpoint.cs b/src/Microsoft.TestPlatform.CommunicationUtilities/Interfaces/ICommunicationEndpoint.cs new file mode 100644 index 0000000000..e44e3e49a9 --- /dev/null +++ b/src/Microsoft.TestPlatform.CommunicationUtilities/Interfaces/ICommunicationEndpoint.cs @@ -0,0 +1,32 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.Interfaces +{ + using System; + + public interface ICommunicationEndPoint + { + /// + /// Event raised when an endPoint is connected. + /// + event EventHandler Connected; + + /// + /// Event raised when an endPoint is disconnected. + /// + event EventHandler Disconnected; + + /// + /// Starts the endPoint and channel. + /// + /// Address to connect + /// Address of the connected endPoint + string Start(string endPoint); + + /// + /// Stops the endPoint and closes the underlying communication channel. + /// + void Stop(); + } +} \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.CommunicationUtilities/LengthPrefixCommunicationChannel.cs b/src/Microsoft.TestPlatform.CommunicationUtilities/LengthPrefixCommunicationChannel.cs index 7e8422f172..48e506e5cb 100644 --- a/src/Microsoft.TestPlatform.CommunicationUtilities/LengthPrefixCommunicationChannel.cs +++ b/src/Microsoft.TestPlatform.CommunicationUtilities/LengthPrefixCommunicationChannel.cs @@ -55,10 +55,9 @@ public Task NotifyDataAvailable() // Try read data even if no one is listening to the data stream. Some server // implementations (like Sockets) depend on the read operation to determine if a // connection is closed. - var data = this.reader.ReadString(); - if (this.MessageReceived != null) { + var data = this.reader.ReadString(); this.MessageReceived.SafeInvoke(this, new MessageReceivedEventArgs { Data = data }, "LengthPrefixCommunicationChannel: MessageReceived"); } diff --git a/src/Microsoft.TestPlatform.CommunicationUtilities/SocketClient.cs b/src/Microsoft.TestPlatform.CommunicationUtilities/SocketClient.cs index 587096c2d6..1fc7739f72 100644 --- a/src/Microsoft.TestPlatform.CommunicationUtilities/SocketClient.cs +++ b/src/Microsoft.TestPlatform.CommunicationUtilities/SocketClient.cs @@ -17,7 +17,7 @@ namespace Microsoft.VisualStudio.TestPlatform.CommunicationUtilities /// /// Communication client implementation over sockets. /// - public class SocketClient : ICommunicationClient + public class SocketClient : ICommunicationEndPoint { private readonly CancellationTokenSource cancellation; private readonly TcpClient tcpClient; @@ -41,16 +41,24 @@ protected SocketClient(Func channelFactory) } /// - public event EventHandler ServerConnected; + public event EventHandler Connected; /// - public event EventHandler ServerDisconnected; + public event EventHandler Disconnected; /// - public void Start(string connectionInfo) + public string Start(string endPoint) { - this.tcpClient.ConnectAsync(IPAddress.Loopback, int.Parse(connectionInfo)) - .ContinueWith(this.OnServerConnected); + var ipEndPoint = endPoint.GetIPEndPoint(); + + if (EqtTrace.IsVerboseEnabled) + { + EqtTrace.Verbose("Waiting for connecting to server"); + } + + // Don't start if the endPoint port is zero + this.tcpClient.ConnectAsync(ipEndPoint.Address, ipEndPoint.Port).ContinueWith(this.OnServerConnected); + return ipEndPoint.ToString(); } /// @@ -65,22 +73,33 @@ public void Stop() private void OnServerConnected(Task connectAsyncTask) { - if (connectAsyncTask.IsFaulted) + if (this.Connected != null) { - throw connectAsyncTask.Exception; - } + if (connectAsyncTask.IsFaulted) + { + this.Connected.SafeInvoke(this, new ConnectedEventArgs(connectAsyncTask.Exception), "SocketClient: ServerConnected"); + if (EqtTrace.IsVerboseEnabled) + { + EqtTrace.Verbose("Unable to connect to server, Exception occured : {0}", connectAsyncTask.Exception); + } + } + else + { + this.channel = this.channelFactory(this.tcpClient.GetStream()); + this.Connected.SafeInvoke(this, new ConnectedEventArgs(this.channel), "SocketClient: ServerConnected"); - this.channel = this.channelFactory(this.tcpClient.GetStream()); - if (this.ServerConnected != null) - { - this.ServerConnected.SafeInvoke(this, new ConnectedEventArgs(this.channel), "SocketClient: ServerConnected"); - - // Start the message loop - Task.Run(() => this.tcpClient.MessageLoopAsync( - this.channel, - this.Stop, - this.cancellation.Token)) - .ConfigureAwait(false); + if (EqtTrace.IsVerboseEnabled) + { + EqtTrace.Verbose("Connected to server, and starting MessageLoopAsync"); + } + + // Start the message loop + Task.Run(() => this.tcpClient.MessageLoopAsync( + this.channel, + this.Stop, + this.cancellation.Token)) + .ConfigureAwait(false); + } } } @@ -102,7 +121,7 @@ private void Stop(Exception error) this.channel.Dispose(); this.cancellation.Dispose(); - this.ServerDisconnected?.SafeInvoke(this, new DisconnectedEventArgs(), "SocketClient: ServerDisconnected"); + this.Disconnected?.SafeInvoke(this, new DisconnectedEventArgs(), "SocketClient: ServerDisconnected"); } } } diff --git a/src/Microsoft.TestPlatform.CommunicationUtilities/SocketServer.cs b/src/Microsoft.TestPlatform.CommunicationUtilities/SocketServer.cs index a514048a1b..8057e48af6 100644 --- a/src/Microsoft.TestPlatform.CommunicationUtilities/SocketServer.cs +++ b/src/Microsoft.TestPlatform.CommunicationUtilities/SocketServer.cs @@ -17,7 +17,7 @@ namespace Microsoft.VisualStudio.TestPlatform.CommunicationUtilities /// /// Communication server implementation over sockets. /// - public class SocketServer : ICommunicationServer + public class SocketServer : ICommunicationEndPoint { private readonly CancellationTokenSource cancellation; @@ -35,7 +35,7 @@ public class SocketServer : ICommunicationServer /// Initializes a new instance of the class. /// public SocketServer() - : this((stream) => new LengthPrefixCommunicationChannel(stream)) + : this(stream => new LengthPrefixCommunicationChannel(stream)) { } @@ -53,21 +53,19 @@ protected SocketServer(Func channelFactory) } /// - public event EventHandler ClientConnected; + public event EventHandler Connected; /// - public event EventHandler ClientDisconnected; + public event EventHandler Disconnected; - /// - public string Start() + public string Start(string endPoint) { - var endpoint = new IPEndPoint(IPAddress.Loopback, 0); - this.tcpListener = new TcpListener(endpoint); + this.tcpListener = new TcpListener(endPoint.GetIPEndPoint()); this.tcpListener.Start(); - var connectionInfo = ((IPEndPoint)this.tcpListener.LocalEndpoint).Port.ToString(); - EqtTrace.Info("SocketServer: Listening on port : {0}", connectionInfo); + var connectionInfo = ((IPEndPoint)this.tcpListener.LocalEndpoint).ToString(); + EqtTrace.Info("SocketServer: Listening on end point : {0}", connectionInfo); // Serves a single client at the moment. An error in connection, or message loop just // terminates the entire server. @@ -90,10 +88,15 @@ private void OnClientConnected(TcpClient client) this.tcpClient = client; this.tcpClient.Client.NoDelay = true; - if (this.ClientConnected != null) + if (this.Connected != null) { this.channel = this.channelFactory(this.tcpClient.GetStream()); - this.ClientConnected.SafeInvoke(this, new ConnectedEventArgs(this.channel), "SocketServer: ClientConnected"); + this.Connected.SafeInvoke(this, new ConnectedEventArgs(this.channel), "SocketServer: ClientConnected"); + + if (EqtTrace.IsVerboseEnabled) + { + EqtTrace.Verbose("Client connected, and starting MessageLoopAsync"); + } // Start the message loop Task.Run(() => this.tcpClient.MessageLoopAsync(this.channel, error => this.Stop(error), this.cancellation.Token)).ConfigureAwait(false); @@ -121,7 +124,7 @@ private void Stop(Exception error) this.channel.Dispose(); this.cancellation.Dispose(); - this.ClientDisconnected?.SafeInvoke(this, new DisconnectedEventArgs { Error = error }, "SocketServer: ClientDisconnected"); + this.Disconnected?.SafeInvoke(this, new DisconnectedEventArgs { Error = error }, "SocketServer: ClientDisconnected"); } } } diff --git a/src/Microsoft.TestPlatform.CommunicationUtilities/TcpClientExtensions.cs b/src/Microsoft.TestPlatform.CommunicationUtilities/TcpClientExtensions.cs index c77871e7e3..5ccc88f2d7 100644 --- a/src/Microsoft.TestPlatform.CommunicationUtilities/TcpClientExtensions.cs +++ b/src/Microsoft.TestPlatform.CommunicationUtilities/TcpClientExtensions.cs @@ -5,6 +5,7 @@ namespace Microsoft.VisualStudio.TestPlatform.CommunicationUtilities { using System; using System.IO; + using System.Net; using System.Net.Sockets; using System.Threading; using System.Threading.Tasks; @@ -69,5 +70,20 @@ internal static Task MessageLoopAsync( return Task.FromResult(0); } + + /// + /// Converts a given string endpoint address to valid Ipv4, Ipv6 IPEndpoint + /// + /// Input endpoint address + /// IPEndpoint from give string, if its not a valid string. It will create endpoint with loop back address with port 0 + internal static IPEndPoint GetIPEndPoint(this string value) + { + if (Uri.TryCreate(string.Concat("tcp://", value), UriKind.Absolute, out Uri uri)) + { + return new IPEndPoint(IPAddress.Parse(uri.Host), uri.Port < 0 ? 0 : uri.Port); + } + + return new IPEndPoint(IPAddress.Loopback, 0); + } } } diff --git a/src/Microsoft.TestPlatform.CommunicationUtilities/TestRequestSender.cs b/src/Microsoft.TestPlatform.CommunicationUtilities/TestRequestSender.cs index 041ec198e7..deae160a5c 100644 --- a/src/Microsoft.TestPlatform.CommunicationUtilities/TestRequestSender.cs +++ b/src/Microsoft.TestPlatform.CommunicationUtilities/TestRequestSender.cs @@ -6,460 +6,561 @@ namespace Microsoft.VisualStudio.TestPlatform.CommunicationUtilities using System; using System.Collections.Generic; using System.Globalization; - using System.IO; + using System.Net; using System.Threading; - using System.Threading.Tasks; + using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.Interfaces; using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.ObjectModel; using Microsoft.VisualStudio.TestPlatform.ObjectModel; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; + using CommonResources = Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.Resources.Resources; /// - /// Utility class that facilitates the IPC comunication. Acts as server. + /// Test request sender implementation. /// - public sealed class TestRequestSender : ITestRequestSender + public class TestRequestSender : ITestRequestSender { - private ICommunicationManager communicationManager; + // Time to wait for test host exit + private const int ClientProcessExitWaitTimeout = 10 * 1000; + + private readonly IDataSerializer dataSerializer; + + private readonly ManualResetEventSlim connected; + + private readonly ManualResetEventSlim clientExited; + + private readonly int clientExitedWaitTime; + + private ICommunicationEndPoint communicationEndpoint; + + private ICommunicationChannel channel; - private ITransport transport; + private EventHandler onMessageReceived; - private bool sendMessagesToRemoteHost = true; + private Action onDisconnected; - private IDataSerializer dataSerializer; + // Set to 1 if Discovery/Execution is complete, i.e. complete handlers have been invoked + private int operationCompleted; + + private ITestMessageEventHandler messageEventHandler; + + private string clientExitErrorMessage; // Set default to 1, if protocol version check does not happen // that implies host is using version 1 private int protocolVersion = 1; private int highestSupportedVersion = 2; - - /// - /// Use to cancel blocking tasks associated with testhost process - /// - private CancellationTokenSource clientExitCancellationSource; - - private string clientExitErrorMessage; + private TestHostConnectionInfo connectionInfo; /// /// Initializes a new instance of the class. /// - /// Protocol related information + /// Protocol configuration. /// Transport layer to set up connection public TestRequestSender(ProtocolConfig protocolConfig, TestHostConnectionInfo connectionInfo) - : this(new SocketCommunicationManager(), connectionInfo, JsonDataSerializer.Instance, protocolConfig) + : this(connectionInfo, JsonDataSerializer.Instance, protocolConfig, ClientProcessExitWaitTimeout) { + this.SetCommunicationEndPoint(); } - /// - /// Initializes a new instance of the class. - /// - /// Communication Manager for sending and receiving messages. - /// ConnectionInfo to set up transport layer - /// Serializer for serialization and deserialization of the messages. - /// Protocol related information - internal TestRequestSender(ICommunicationManager communicationManager, TestHostConnectionInfo connectionInfo, IDataSerializer dataSerializer, ProtocolConfig protocolConfig) + internal TestRequestSender( + TestHostConnectionInfo connectionInfo, + IDataSerializer serializer, + ProtocolConfig protocolConfig, + int clientExitedWaitTime) { + this.dataSerializer = serializer; + this.connected = new ManualResetEventSlim(false); + this.clientExited = new ManualResetEventSlim(false); + this.clientExitedWaitTime = clientExitedWaitTime; + this.operationCompleted = 0; + this.highestSupportedVersion = protocolConfig.Version; - this.communicationManager = communicationManager; // The connectionInfo here is that of RuntimeProvider, so reverse the role of runner. - connectionInfo.Role = connectionInfo.Role == ConnectionRole.Host - ? ConnectionRole.Client - : ConnectionRole.Host; + this.connectionInfo.Endpoint = connectionInfo.Endpoint; + this.connectionInfo.Role = connectionInfo.Role == ConnectionRole.Host + ? ConnectionRole.Client + : ConnectionRole.Host; + } - this.transport = new SocketTransport(communicationManager, connectionInfo); - this.dataSerializer = dataSerializer; + /// + /// Initializes a new instance of the class. + /// Used only for testing to inject communication endpoint. + /// + /// Communication server implementation. + /// ConnectionInfo to set up transport layer + /// Serializer implementation. + /// Protocol configuration. + /// Time to wait for client process exit. + internal TestRequestSender( + ICommunicationEndPoint communicationEndPoint, + TestHostConnectionInfo connectionInfo, + IDataSerializer serializer, + ProtocolConfig protocolConfig, + int clientExitedWaitTime) + : this(connectionInfo, serializer, protocolConfig, clientExitedWaitTime) + { + this.communicationEndpoint = communicationEndPoint; } - /// + /// public int InitializeCommunication() { - this.clientExitCancellationSource = new CancellationTokenSource(); + // this.clientExitCancellationSource = new CancellationTokenSource(); this.clientExitErrorMessage = string.Empty; - return this.transport.Initialize().Port; - } - /// - public bool WaitForRequestHandlerConnection(int clientConnectionTimeout) - { - return this.transport.WaitForConnection(clientConnectionTimeout); - } + this.communicationEndpoint.Connected += (sender, args) => + { + this.channel = args.Channel; + this.connected.Set(); + }; + this.communicationEndpoint.Disconnected += (sender, args) => + { + // If there's an disconnected event handler, call it + this.onDisconnected?.Invoke(args); + }; - /// - public void Dispose() - { - this.transport.Dispose(); + // Server start returns the listener port + // return int.Parse(this.communicationServer.Start()); + var endpoint = this.communicationEndpoint.Start(this.connectionInfo.Endpoint); + return endpoint.GetIPEndPoint().Port; } - /// - public void Close() + /// + public bool WaitForRequestHandlerConnection(int connectionTimeout) { - this.Dispose(); - EqtTrace.Info("Closing the connection"); + return this.connected.Wait(connectionTimeout); } - /// + /// public void CheckVersionWithTestHost() { - this.communicationManager.SendMessage(MessageType.VersionCheck, payload: this.highestSupportedVersion); + // Negotiation follows these steps: + // Runner sends highest supported version to Test host + // Test host sends the version it can support (must be less than highest) to runner + // Error case: test host can send a protocol error if it cannot find a supported version + var protocolNegotiated = new ManualResetEvent(false); + this.onMessageReceived = (sender, args) => + { + var message = this.dataSerializer.DeserializeMessage(args.Data); - var message = this.communicationManager.ReceiveMessage(); + if (message.MessageType == MessageType.VersionCheck) + { + this.protocolVersion = this.dataSerializer.DeserializePayload(message); - if (message.MessageType == MessageType.VersionCheck) - { - this.protocolVersion = this.dataSerializer.DeserializePayload(message); + EqtTrace.Info(@"TestRequestSender: VersionCheck Succeeded, NegotiatedVersion = {0}", this.protocolVersion); + } - EqtTrace.Info("TestRequestSender: VersionCheck Succeeded, NegotiatedVersion = {0}", this.protocolVersion); - } - else if (message.MessageType == MessageType.ProtocolError) + // TRH can also send TestMessage if tracing is enabled, so log it at runner end + else if (message.MessageType == MessageType.TestMessage) + { + // Only Deserialize if Tracing is enabled + if (EqtTrace.IsInfoEnabled) + { + var testMessagePayload = this.dataSerializer.DeserializePayload(message); + EqtTrace.Info("TestRequestSender.CheckVersionWithTestHost: " + testMessagePayload.Message); + } + } + else if (message.MessageType == MessageType.ProtocolError) + { + throw new TestPlatformException(string.Format(CultureInfo.CurrentUICulture, CommonResources.VersionCheckFailed)); + } + else + { + throw new TestPlatformException(string.Format( + CultureInfo.CurrentUICulture, + CommonResources.UnexpectedMessage, + MessageType.VersionCheck, + message.MessageType)); + } + + protocolNegotiated.Set(); + }; + this.channel.MessageReceived += this.onMessageReceived; + + try { - // TODO : Payload for ProtocolError needs to finalized. - throw new TestPlatformException(string.Format(CultureInfo.CurrentUICulture, CommonResources.VersionCheckFailed)); + // Send the protocol negotiation request. Note that we always serialize this data + // without any versioning in the message itself. + var data = this.dataSerializer.SerializePayload(MessageType.VersionCheck, this.highestSupportedVersion); + this.channel.Send(data); + + // Wait for negotiation response + if (!protocolNegotiated.WaitOne(60 * 1000)) + { + throw new TestPlatformException(string.Format(CultureInfo.CurrentUICulture, CommonResources.VersionCheckTimedout)); + } } - else + finally { - throw new TestPlatformException(string.Format(CultureInfo.CurrentUICulture, CommonResources.UnexpectedMessage, MessageType.VersionCheck, message.MessageType)); + this.channel.MessageReceived -= this.onMessageReceived; + this.onMessageReceived = null; } } - /// - public void InitializeDiscovery(IEnumerable pathToAdditionalExtensions) - { - this.communicationManager.SendMessage(MessageType.DiscoveryInitialize, pathToAdditionalExtensions, version: this.protocolVersion); - } + #region Discovery Protocol - /// - public void InitializeExecution(IEnumerable pathToAdditionalExtensions) + /// + public void InitializeDiscovery(IEnumerable pathToAdditionalExtensions) { - this.communicationManager.SendMessage(MessageType.ExecutionInitialize, pathToAdditionalExtensions, version: this.protocolVersion); + var message = this.dataSerializer.SerializePayload( + MessageType.DiscoveryInitialize, + pathToAdditionalExtensions, + this.protocolVersion); + this.channel.Send(message); } /// public void DiscoverTests(DiscoveryCriteria discoveryCriteria, ITestDiscoveryEventsHandler2 discoveryEventsHandler) { - try - { - this.communicationManager.SendMessage(MessageType.StartDiscovery, discoveryCriteria, version: this.protocolVersion); - - var isDiscoveryComplete = false; - - // Cycle through the messages that the testhost sends. - // Currently each of the operations are not separate tasks since they should not each take much time. This is just a notification. - while (!isDiscoveryComplete) + this.messageEventHandler = discoveryEventsHandler; + this.onDisconnected = (disconnectedEventArgs) => { - var rawMessage = this.TryReceiveRawMessage(); - if (EqtTrace.IsVerboseEnabled) - { - EqtTrace.Verbose("Received message: {0}", rawMessage); - } - - // Send raw message first to unblock handlers waiting to send message to IDEs - discoveryEventsHandler.HandleRawMessage(rawMessage); - - var message = this.dataSerializer.DeserializeMessage(rawMessage); - if (string.Equals(MessageType.TestCasesFound, message.MessageType)) - { - var testCases = this.dataSerializer.DeserializePayload>(message); - discoveryEventsHandler.HandleDiscoveredTests(testCases); - } - else if (string.Equals(MessageType.DiscoveryComplete, message.MessageType)) - { - var discoveryCompletePayload = this.dataSerializer.DeserializePayload(message); - - var discoveryCompleteEventArgs = new DiscoveryCompleteEventArgs(discoveryCompletePayload.TotalTests, discoveryCompletePayload.IsAborted); - discoveryCompleteEventArgs.Metrics = discoveryCompletePayload.Metrics; - discoveryEventsHandler.HandleDiscoveryComplete( - discoveryCompleteEventArgs, - discoveryCompletePayload.LastDiscoveredTests); - isDiscoveryComplete = true; - } - else if (string.Equals(MessageType.TestMessage, message.MessageType)) - { - var testMessagePayload = this.dataSerializer.DeserializePayload(message); - discoveryEventsHandler.HandleLogMessage( - testMessagePayload.MessageLevel, - testMessagePayload.Message); - } - } - } - catch (Exception ex) - { - this.OnDiscoveryAbort(discoveryEventsHandler, ex); - } + this.OnDiscoveryAbort(discoveryEventsHandler, disconnectedEventArgs.Error, true); + }; + this.onMessageReceived = (sender, args) => this.OnDiscoveryMessageReceived(discoveryEventsHandler, args); + + this.channel.MessageReceived += this.onMessageReceived; + var message = this.dataSerializer.SerializePayload( + MessageType.StartDiscovery, + discoveryCriteria, + this.protocolVersion); + this.channel.Send(message); } + #endregion - /// - /// Ends the session with the test host. - /// - public void EndSession() - { - // don't try to communicate if connection is broken - if (!this.sendMessagesToRemoteHost) - { - EqtTrace.Error("Connection has been broken: not sending SessionEnd message"); - return; - } + #region Execution Protocol - this.communicationManager.SendMessage(MessageType.SessionEnd); + /// + public void InitializeExecution(IEnumerable pathToAdditionalExtensions) + { + var message = this.dataSerializer.SerializePayload( + MessageType.ExecutionInitialize, + pathToAdditionalExtensions, + this.protocolVersion); + this.channel.Send(message); } - /// - /// Executes tests on the sources specified with the criteria mentioned. - /// - /// The test run criteria. - /// The handler for execution events from the test host. + /// public void StartTestRun(TestRunCriteriaWithSources runCriteria, ITestRunEventsHandler eventHandler) { - this.StartTestRunAndListenAndReportTestResults(MessageType.StartTestExecutionWithSources, runCriteria, eventHandler); + this.messageEventHandler = eventHandler; + this.onDisconnected = (disconnectedEventArgs) => + { + this.OnTestRunAbort(eventHandler, disconnectedEventArgs.Error, true); + }; + this.onMessageReceived = (sender, args) => this.OnExecutionMessageReceived(sender, args, eventHandler); + this.channel.MessageReceived += this.onMessageReceived; + + var message = this.dataSerializer.SerializePayload( + MessageType.StartTestExecutionWithSources, + runCriteria, + this.protocolVersion); + this.channel.Send(message); } - /// - /// Executes the specified tests with the criteria mentioned. - /// - /// The test run criteria. - /// The handler for execution events from the test host. + /// public void StartTestRun(TestRunCriteriaWithTests runCriteria, ITestRunEventsHandler eventHandler) { - this.StartTestRunAndListenAndReportTestResults(MessageType.StartTestExecutionWithTests, runCriteria, eventHandler); + this.messageEventHandler = eventHandler; + this.onDisconnected = (disconnectedEventArgs) => + { + this.OnTestRunAbort(eventHandler, disconnectedEventArgs.Error, true); + }; + this.onMessageReceived = (sender, args) => this.OnExecutionMessageReceived(sender, args, eventHandler); + this.channel.MessageReceived += this.onMessageReceived; + + var message = this.dataSerializer.SerializePayload( + MessageType.StartTestExecutionWithTests, + runCriteria, + this.protocolVersion); + this.channel.Send(message); } - /// - /// Send the cancel message to test host - /// + /// public void SendTestRunCancel() { - this.communicationManager.SendMessage(MessageType.CancelTestRun); + this.channel?.Send(this.dataSerializer.SerializeMessage(MessageType.CancelTestRun)); } - /// - /// Send the Abort test run message - /// + /// public void SendTestRunAbort() { - this.communicationManager.SendMessage(MessageType.AbortTestRun); + this.channel?.Send(this.dataSerializer.SerializeMessage(MessageType.AbortTestRun)); } - /// - /// Handles exit of the client process. - /// - /// Standard Error. + #endregion + + /// + public void EndSession() + { + if (!this.IsOperationComplete()) + { + this.channel.Send(this.dataSerializer.SerializeMessage(MessageType.SessionEnd)); + } + } + + /// public void OnClientProcessExit(string stdError) { + // This method is called on test host exit. If test host has any errors, stdError + // provides the crash call stack. + EqtTrace.Info("TestRequestSender.OnClientProcessExit: Test host process exited. Standard error: " + stdError); this.clientExitErrorMessage = stdError; - this.clientExitCancellationSource.Cancel(); + this.clientExited.Set(); + + // Note that we're not explicitly disconnecting the communication channel; wait for the + // channel to determine the disconnection on its own. } - private void StartTestRunAndListenAndReportTestResults( - string messageType, - object payload, - ITestRunEventsHandler eventHandler) + /// + public void Close() { - try - { - this.communicationManager.SendMessage(messageType, payload, version: this.protocolVersion); + this.Dispose(); + EqtTrace.Info("Closing the connection"); + } - // This needs to happen asynchronously. - Task.Run(() => this.ListenAndReportTestResults(eventHandler)); - } - catch (Exception exception) + /// + public void Dispose() + { + if (this.channel != null) { - this.OnTestRunAbort(eventHandler, exception); + this.channel.MessageReceived -= this.onMessageReceived; } + + this.communicationEndpoint.Stop(); } - private void ListenAndReportTestResults(ITestRunEventsHandler testRunEventsHandler) + private void OnExecutionMessageReceived(object sender, MessageReceivedEventArgs messageReceived, ITestRunEventsHandler testRunEventsHandler) { - var isTestRunComplete = false; - - // Cycle through the messages that the testhost sends. - // Currently each of the operations are not separate tasks since they should not each take much time. This is just a notification. - while (!isTestRunComplete) + try { - try - { - var rawMessage = this.TryReceiveRawMessage(); + var rawMessage = messageReceived.Data; - // Send raw message first to unblock handlers waiting to send message to IDEs - testRunEventsHandler.HandleRawMessage(rawMessage); + // Send raw message first to unblock handlers waiting to send message to IDEs + testRunEventsHandler.HandleRawMessage(rawMessage); - var message = this.dataSerializer.DeserializeMessage(rawMessage); - if (string.Equals(MessageType.TestRunStatsChange, message.MessageType)) - { - var testRunChangedArgs = this.dataSerializer.DeserializePayload( - message); + var message = this.dataSerializer.DeserializeMessage(rawMessage); + switch (message.MessageType) + { + case MessageType.TestRunStatsChange: + var testRunChangedArgs = this.dataSerializer.DeserializePayload(message); testRunEventsHandler.HandleTestRunStatsChange(testRunChangedArgs); - } - else if (string.Equals(MessageType.ExecutionComplete, message.MessageType)) - { - var testRunCompletePayload = - this.dataSerializer.DeserializePayload(message); + break; + case MessageType.ExecutionComplete: + var testRunCompletePayload = this.dataSerializer.DeserializePayload(message); testRunEventsHandler.HandleTestRunComplete( testRunCompletePayload.TestRunCompleteArgs, testRunCompletePayload.LastRunTests, testRunCompletePayload.RunAttachments, testRunCompletePayload.ExecutorUris); - isTestRunComplete = true; - } - else if (string.Equals(MessageType.TestMessage, message.MessageType)) - { + + this.SetOperationComplete(); + break; + case MessageType.TestMessage: var testMessagePayload = this.dataSerializer.DeserializePayload(message); - testRunEventsHandler.HandleLogMessage( - testMessagePayload.MessageLevel, - testMessagePayload.Message); - } - else if (string.Equals(MessageType.LaunchAdapterProcessWithDebuggerAttached, message.MessageType)) - { + testRunEventsHandler.HandleLogMessage(testMessagePayload.MessageLevel, testMessagePayload.Message); + break; + case MessageType.LaunchAdapterProcessWithDebuggerAttached: var testProcessStartInfo = this.dataSerializer.DeserializePayload(message); int processId = testRunEventsHandler.LaunchProcessWithDebuggerAttached(testProcessStartInfo); - this.communicationManager.SendMessage( - MessageType.LaunchAdapterProcessWithDebuggerAttachedCallback, - processId, - version: this.protocolVersion); - } - } - catch (IOException exception) - { - // To avoid further communication with remote host - this.sendMessagesToRemoteHost = false; + var data = + this.dataSerializer.SerializePayload( + MessageType.LaunchAdapterProcessWithDebuggerAttachedCallback, + processId, + this.protocolVersion); - this.OnTestRunAbort(testRunEventsHandler, exception); - isTestRunComplete = true; - } - catch (Exception exception) - { - this.OnTestRunAbort(testRunEventsHandler, exception); - isTestRunComplete = true; + this.channel.Send(data); + break; } } - } - - private void CleanupCommunicationIfProcessExit() - { - if (this.clientExitCancellationSource != null && this.clientExitCancellationSource.IsCancellationRequested) + catch (Exception exception) { - this.communicationManager.StopServer(); + this.OnTestRunAbort(testRunEventsHandler, exception, false); } } - private void OnTestRunAbort(ITestRunEventsHandler testRunEventsHandler, Exception exception) + private void OnDiscoveryMessageReceived(ITestDiscoveryEventsHandler2 discoveryEventsHandler, MessageReceivedEventArgs args) { try { - EqtTrace.Error("Server: TestExecution: Aborting test run because {0}", exception); + var rawMessage = args.Data; - var reason = string.Format(CommonResources.AbortedTestRun, exception?.Message); - - // log console message to vstest console - testRunEventsHandler.HandleLogMessage(TestMessageLevel.Error, reason); - - // log console message to vstest console wrapper - var testMessagePayload = new TestMessagePayload { MessageLevel = TestMessageLevel.Error, Message = reason }; - var rawMessage = this.dataSerializer.SerializePayload(MessageType.TestMessage, testMessagePayload); - testRunEventsHandler.HandleRawMessage(rawMessage); - - // notify test run abort to vstest console wrapper. - var completeArgs = new TestRunCompleteEventArgs(null, false, true, exception, null, TimeSpan.Zero); - var payload = new TestRunCompletePayload { TestRunCompleteArgs = completeArgs }; - rawMessage = this.dataSerializer.SerializePayload(MessageType.ExecutionComplete, payload); - testRunEventsHandler.HandleRawMessage(rawMessage); + // Currently each of the operations are not separate tasks since they should not each take much time. This is just a notification. + if (EqtTrace.IsInfoEnabled) + { + EqtTrace.Info("TestRequestSender: Received message: {0}", rawMessage); + } - // notify of a test run complete and bail out. - testRunEventsHandler.HandleTestRunComplete(completeArgs, null, null, null); + // Send raw message first to unblock handlers waiting to send message to IDEs + discoveryEventsHandler.HandleRawMessage(rawMessage); - this.CleanupCommunicationIfProcessExit(); + var data = this.dataSerializer.DeserializeMessage(rawMessage); + switch (data.MessageType) + { + case MessageType.TestCasesFound: + var testCases = this.dataSerializer.DeserializePayload>(data); + discoveryEventsHandler.HandleDiscoveredTests(testCases); + break; + case MessageType.DiscoveryComplete: + var discoveryCompletePayload = + this.dataSerializer.DeserializePayload(data); + var discoveryCompleteEventArgs = new DiscoveryCompleteEventArgs(discoveryCompletePayload.TotalTests, discoveryCompletePayload.IsAborted); + discoveryCompleteEventArgs.Metrics = discoveryCompletePayload.Metrics; + discoveryEventsHandler.HandleDiscoveryComplete( + discoveryCompleteEventArgs, + discoveryCompletePayload.LastDiscoveredTests); + this.SetOperationComplete(); + break; + case MessageType.TestMessage: + var testMessagePayload = this.dataSerializer.DeserializePayload( + data); + discoveryEventsHandler.HandleLogMessage( + testMessagePayload.MessageLevel, + testMessagePayload.Message); + break; + } } catch (Exception ex) { - EqtTrace.Error(ex); - throw ex; + this.OnDiscoveryAbort(discoveryEventsHandler, ex, false); } } - private void OnDiscoveryAbort(ITestDiscoveryEventsHandler2 eventHandler, Exception exception) + private void OnTestRunAbort(ITestRunEventsHandler testRunEventsHandler, Exception exception, bool getClientError) { - try + if (this.IsOperationComplete()) { - EqtTrace.Error("Server: TestExecution: Aborting test discovery because {0}", exception); + EqtTrace.Verbose("TestRequestSender: OnTestRunAbort: Operation is already complete. Skip error message."); + return; + } - var reason = string.Format(CommonResources.AbortedTestDiscovery, exception?.Message); + EqtTrace.Verbose("TestRequestSender: OnTestRunAbort: Set operation complete."); + this.SetOperationComplete(); - // Log to vstest console - eventHandler.HandleLogMessage(TestMessageLevel.Error, reason); + var reason = this.GetAbortErrorMessage(exception, getClientError); + EqtTrace.Error("TestRequestSender: Aborting test run because {0}", reason); + this.LogErrorMessage(string.Format(CommonResources.AbortedTestRun, reason)); - // Log to vs ide test output - var testMessagePayload = new TestMessagePayload { MessageLevel = TestMessageLevel.Error, Message = reason }; - var rawMessage = this.dataSerializer.SerializePayload(MessageType.TestMessage, testMessagePayload); - eventHandler.HandleRawMessage(rawMessage); + // notify test run abort to vstest console wrapper. + var completeArgs = new TestRunCompleteEventArgs(null, false, true, exception, null, TimeSpan.Zero); + var payload = new TestRunCompletePayload { TestRunCompleteArgs = completeArgs }; + var rawMessage = this.dataSerializer.SerializePayload(MessageType.ExecutionComplete, payload); + testRunEventsHandler.HandleRawMessage(rawMessage); - // Notify discovery abort to IDE test output - var payload = new DiscoveryCompletePayload() - { - IsAborted = true, - LastDiscoveredTests = null, - TotalTests = -1 - }; - rawMessage = this.dataSerializer.SerializePayload(MessageType.DiscoveryComplete, payload); - eventHandler.HandleRawMessage(rawMessage); + // notify of a test run complete and bail out. + testRunEventsHandler.HandleTestRunComplete(completeArgs, null, null, null); + } - // Complete discovery - var discoveryCompleteEventArgs = new DiscoveryCompleteEventArgs(-1, true); + private void OnDiscoveryAbort(ITestDiscoveryEventsHandler2 eventHandler, Exception exception, bool getClientError) + { + if (this.IsOperationComplete()) + { + EqtTrace.Verbose("TestRequestSender: OnDiscoveryAbort: Operation is already complete. Skip error message."); + return; + } - eventHandler.HandleDiscoveryComplete(discoveryCompleteEventArgs, null); + EqtTrace.Verbose("TestRequestSender: OnDiscoveryAbort: Set operation complete."); + this.SetOperationComplete(); - this.CleanupCommunicationIfProcessExit(); - } - catch (Exception ex) + var discoveryCompleteEventArgs = new DiscoveryCompleteEventArgs(-1, true); + var reason = this.GetAbortErrorMessage(exception, getClientError); + EqtTrace.Error("TestRequestSender: Aborting test discovery because {0}", reason); + this.LogErrorMessage(string.Format(CommonResources.AbortedTestDiscovery, reason)); + + // Notify discovery abort to IDE test output + var payload = new DiscoveryCompletePayload() { - EqtTrace.Error(ex); - throw ex; - } + IsAborted = true, + LastDiscoveredTests = null, + TotalTests = -1 + }; + var rawMessage = this.dataSerializer.SerializePayload(MessageType.DiscoveryComplete, payload); + eventHandler.HandleRawMessage(rawMessage); + + // Complete discovery + eventHandler.HandleDiscoveryComplete(discoveryCompleteEventArgs, null); } - private string TryReceiveRawMessage() + private string GetAbortErrorMessage(Exception exception, bool getClientError) { - string message = null; - var receiverMessageTask = this.communicationManager.ReceiveRawMessageAsync(this.clientExitCancellationSource.Token); - receiverMessageTask.Wait(); - message = receiverMessageTask.Result; + EqtTrace.Verbose("TestRequestSender: GetAbortErrorMessage: Exception: " + exception); - if (message == null) + // It is also possible for an operation to abort even if client has not + // disconnected, e.g. if there's an error parsing the response from test host. We + // want the exception to be available in those scenarios. + var reason = exception?.Message; + if (getClientError) { - EqtTrace.Warning("TestRequestSender: Communication channel with test host is broken."); - var reason = CommonResources.UnableToCommunicateToTestHost; - if (!string.IsNullOrWhiteSpace(this.clientExitErrorMessage)) + EqtTrace.Verbose("TestRequestSender: GetAbortErrorMessage: Client has disconnected. Wait for standard error."); + + // Set a default message and wait for test host to exit for a moment + reason = CommonResources.UnableToCommunicateToTestHost; + if (this.clientExited.Wait(this.clientExitedWaitTime)) { + EqtTrace.Info("TestRequestSender: GetAbortErrorMessage: Received test host error message."); reason = this.clientExitErrorMessage; } - else - { - // Test host process has not exited yet. We will wait for exit to allow us gather - // standard error - var processWaitEvent = new ManualResetEventSlim(); - try - { - EqtTrace.Info("TestRequestSender: Waiting for test host to exit."); - processWaitEvent.Wait(TimeSpan.FromSeconds(10), this.clientExitCancellationSource.Token); - // Use a default error message - EqtTrace.Info("TestRequestSender: Timed out waiting for test host to exit."); - reason = CommonResources.UnableToCommunicateToTestHost; - } - catch (OperationCanceledException) - { - EqtTrace.Info("TestRequestSender: Got error message from test host."); - reason = this.clientExitErrorMessage; - } - } + EqtTrace.Info("TestRequestSender: GetAbortErrorMessage: Timed out waiting for test host error message."); + } + + return reason; + } - EqtTrace.Error("TestRequestSender: Unable to receive message from testhost: {0}", reason); - throw new IOException(reason); + private void LogErrorMessage(string message) + { + if (this.messageEventHandler == null) + { + EqtTrace.Error("TestRequestSender.LogErrorMessage: Message event handler not set. Error: " + message); + return; } - return message; + // Log to vstest console + this.messageEventHandler.HandleLogMessage(TestMessageLevel.Error, message); + + // Log to vs ide test output + var testMessagePayload = new TestMessagePayload { MessageLevel = TestMessageLevel.Error, Message = message }; + var rawMessage = this.dataSerializer.SerializePayload(MessageType.TestMessage, testMessagePayload); + this.messageEventHandler.HandleRawMessage(rawMessage); + } + + private bool IsOperationComplete() + { + return this.operationCompleted == 1; + } + + private void SetOperationComplete() + { + // Complete the currently ongoing operation (Discovery/Execution) + Interlocked.CompareExchange(ref this.operationCompleted, 1, 0); + } + + private void SetCommunicationEndPoint() + { + // TODO: Use factory to get the communication endpoint. It will abstract out the type of communication endpoint like socket, shared memory or named pipe etc., + if (this.connectionInfo.Role == ConnectionRole.Client) + { + this.communicationEndpoint = new SocketClient(); + if (EqtTrace.IsVerboseEnabled) + { + EqtTrace.Verbose("TestRequestSender is acting as client"); + } + } + else + { + this.communicationEndpoint = new SocketServer(); + if (EqtTrace.IsVerboseEnabled) + { + EqtTrace.Verbose("TestRequestSender is acting as server"); + } + } } } } diff --git a/src/Microsoft.TestPlatform.CommunicationUtilities/TestRequestSender2.cs b/src/Microsoft.TestPlatform.CommunicationUtilities/TestRequestSender2.cs deleted file mode 100644 index 81f7cba580..0000000000 --- a/src/Microsoft.TestPlatform.CommunicationUtilities/TestRequestSender2.cs +++ /dev/null @@ -1,507 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -namespace Microsoft.VisualStudio.TestPlatform.CommunicationUtilities -{ - using System; - using System.Collections.Generic; - using System.Globalization; - using System.Threading; - using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.Interfaces; - using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.ObjectModel; - using Microsoft.VisualStudio.TestPlatform.ObjectModel; - using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; - using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; - using CommonResources = Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.Resources.Resources; - - /// - /// Test request sender implementation. - /// - public class TestRequestSender2 : ITestRequestSender - { - // Time to wait for test host exit (in seconds) - private const int CLIENTPROCESSEXITWAIT = 10 * 1000; - - private readonly ICommunicationServer communicationServer; - - private readonly IDataSerializer dataSerializer; - - private readonly ManualResetEventSlim connected; - - private readonly ManualResetEventSlim clientExited; - - private readonly int clientExitedWaitTime; - - private ICommunicationChannel channel; - - private EventHandler onMessageReceived; - - private Action onDisconnected; - - // Set to 1 if Discovery/Execution is complete, i.e. complete handlers have been invoked - private int operationCompleted; - - private ITestMessageEventHandler messageEventHandler; - - private string clientExitErrorMessage; - - // Set default to 1, if protocol version check does not happen - // that implies host is using version 1 - private int protocolVersion = 1; - - private int highestSupportedVersion = 2; - - /// - /// Initializes a new instance of the class. - /// - /// Protocol configuration. - public TestRequestSender2(ProtocolConfig protocolConfig) - : this(new SocketServer(), JsonDataSerializer.Instance, protocolConfig, CLIENTPROCESSEXITWAIT) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// Communication server implementation. - /// Serializer implementation. - /// Protocol configuration. - /// Time to wait for client process exit. - protected TestRequestSender2( - ICommunicationServer server, - IDataSerializer serializer, - ProtocolConfig protocolConfig, - int clientExitedWaitTime) - { - this.communicationServer = server; - this.dataSerializer = serializer; - this.connected = new ManualResetEventSlim(false); - this.clientExited = new ManualResetEventSlim(false); - this.clientExitedWaitTime = clientExitedWaitTime; - this.operationCompleted = 0; - - this.highestSupportedVersion = protocolConfig.Version; - } - - /// - public int InitializeCommunication() - { - this.communicationServer.ClientConnected += (sender, args) => - { - this.channel = args.Channel; - this.connected.Set(); - }; - this.communicationServer.ClientDisconnected += (sender, args) => - { - // If there's an disconnected event handler, call it - this.onDisconnected?.Invoke(args); - }; - - // Server start returns the listener port - return int.Parse(this.communicationServer.Start()); - } - - /// - public bool WaitForRequestHandlerConnection(int connectionTimeout) - { - return this.connected.Wait(connectionTimeout); - } - - /// - public void CheckVersionWithTestHost() - { - // Negotiation follows these steps: - // Runner sends highest supported version to Test host - // Test host sends the version it can support (must be less than highest) to runner - // Error case: test host can send a protocol error if it cannot find a supported version - var protocolNegotiated = new ManualResetEvent(false); - this.onMessageReceived = (sender, args) => - { - var message = this.dataSerializer.DeserializeMessage(args.Data); - - if (message.MessageType == MessageType.VersionCheck) - { - this.protocolVersion = this.dataSerializer.DeserializePayload(message); - - EqtTrace.Info(@"TestRequestSender: VersionCheck Succeeded, NegotiatedVersion = {0}", this.protocolVersion); - } - else if (message.MessageType == MessageType.ProtocolError) - { - throw new TestPlatformException(string.Format(CultureInfo.CurrentUICulture, CommonResources.VersionCheckFailed)); - } - else - { - throw new TestPlatformException(string.Format( - CultureInfo.CurrentUICulture, - CommonResources.UnexpectedMessage, - MessageType.VersionCheck, - message.MessageType)); - } - - protocolNegotiated.Set(); - }; - this.channel.MessageReceived += this.onMessageReceived; - - try - { - // Send the protocol negotiation request. Note that we always serialize this data - // without any versioning in the message itself. - var data = this.dataSerializer.SerializePayload(MessageType.VersionCheck, this.highestSupportedVersion); - this.channel.Send(data); - - // Wait for negotiation response - if (!protocolNegotiated.WaitOne(60 * 1000)) - { - throw new TestPlatformException(string.Format(CultureInfo.CurrentUICulture, CommonResources.VersionCheckTimedout)); - } - } - finally - { - this.channel.MessageReceived -= this.onMessageReceived; - this.onMessageReceived = null; - } - } - - #region Discovery Protocol - - /// - public void InitializeDiscovery(IEnumerable pathToAdditionalExtensions) - { - var message = this.dataSerializer.SerializePayload( - MessageType.DiscoveryInitialize, - pathToAdditionalExtensions, - this.protocolVersion); - this.channel.Send(message); - } - - /// - public void DiscoverTests(DiscoveryCriteria discoveryCriteria, ITestDiscoveryEventsHandler2 discoveryEventsHandler) - { - this.messageEventHandler = discoveryEventsHandler; - this.onDisconnected = (disconnectedEventArgs) => - { - this.OnDiscoveryAbort(discoveryEventsHandler, disconnectedEventArgs.Error, true); - }; - this.onMessageReceived = (sender, args) => - { - try - { - var rawMessage = args.Data; - - // Currently each of the operations are not separate tasks since they should not each take much time. This is just a notification. - if (EqtTrace.IsInfoEnabled) - { - EqtTrace.Info("TestRequestSender: Received message: {0}", rawMessage); - } - - // Send raw message first to unblock handlers waiting to send message to IDEs - discoveryEventsHandler.HandleRawMessage(rawMessage); - - var data = this.dataSerializer.DeserializeMessage(rawMessage); - switch (data.MessageType) - { - case MessageType.TestCasesFound: - var testCases = this.dataSerializer.DeserializePayload>(data); - discoveryEventsHandler.HandleDiscoveredTests(testCases); - break; - case MessageType.DiscoveryComplete: - var discoveryCompletePayload = - this.dataSerializer.DeserializePayload(data); - - var discoveryCompleteEventsArgs = new DiscoveryCompleteEventArgs(discoveryCompletePayload.TotalTests, discoveryCompletePayload.IsAborted); - discoveryCompleteEventsArgs.Metrics = discoveryCompletePayload.Metrics; - discoveryEventsHandler.HandleDiscoveryComplete( - discoveryCompleteEventsArgs, - discoveryCompletePayload.LastDiscoveredTests); - - this.SetOperationComplete(); - break; - case MessageType.TestMessage: - var testMessagePayload = this.dataSerializer.DeserializePayload( - data); - discoveryEventsHandler.HandleLogMessage( - testMessagePayload.MessageLevel, - testMessagePayload.Message); - break; - } - } - catch (Exception ex) - { - this.OnDiscoveryAbort(discoveryEventsHandler, ex, false); - } - }; - - this.channel.MessageReceived += this.onMessageReceived; - var message = this.dataSerializer.SerializePayload( - MessageType.StartDiscovery, - discoveryCriteria, - this.protocolVersion); - this.channel.Send(message); - } - - #endregion - - #region Execution Protocol - - /// - public void InitializeExecution(IEnumerable pathToAdditionalExtensions) - { - var message = this.dataSerializer.SerializePayload( - MessageType.ExecutionInitialize, - pathToAdditionalExtensions, - this.protocolVersion); - this.channel.Send(message); - } - - /// - public void StartTestRun(TestRunCriteriaWithSources runCriteria, ITestRunEventsHandler eventHandler) - { - this.messageEventHandler = eventHandler; - this.onDisconnected = (disconnectedEventArgs) => - { - this.OnTestRunAbort(eventHandler, disconnectedEventArgs.Error, true); - }; - this.onMessageReceived = (sender, args) => this.OnExecutionMessageReceived(sender, args, eventHandler); - this.channel.MessageReceived += this.onMessageReceived; - - var message = this.dataSerializer.SerializePayload( - MessageType.StartTestExecutionWithSources, - runCriteria, - this.protocolVersion); - this.channel.Send(message); - } - - /// - public void StartTestRun(TestRunCriteriaWithTests runCriteria, ITestRunEventsHandler eventHandler) - { - this.messageEventHandler = eventHandler; - this.onDisconnected = (disconnectedEventArgs) => - { - this.OnTestRunAbort(eventHandler, disconnectedEventArgs.Error, true); - }; - this.onMessageReceived = (sender, args) => this.OnExecutionMessageReceived(sender, args, eventHandler); - this.channel.MessageReceived += this.onMessageReceived; - - var message = this.dataSerializer.SerializePayload( - MessageType.StartTestExecutionWithTests, - runCriteria, - this.protocolVersion); - this.channel.Send(message); - } - - /// - public void SendTestRunCancel() - { - this.channel?.Send(this.dataSerializer.SerializeMessage(MessageType.CancelTestRun)); - } - - /// - public void SendTestRunAbort() - { - this.channel?.Send(this.dataSerializer.SerializeMessage(MessageType.AbortTestRun)); - } - - #endregion - - /// - public void EndSession() - { - if (!this.IsOperationComplete()) - { - this.channel.Send(this.dataSerializer.SerializeMessage(MessageType.SessionEnd)); - } - } - - /// - public void OnClientProcessExit(string stdError) - { - // This method is called on test host exit. If test host has any errors, stdError - // provides the crash call stack. - EqtTrace.Info("TestRequestSender.OnClientProcessExit: Test host process exited. Standard error: " + stdError); - this.clientExitErrorMessage = stdError; - this.clientExited.Set(); - - // Note that we're not explicitly disconnecting the communication channel; wait for the - // channel to determine the disconnection on its own. - } - - /// - public void Close() - { - this.Dispose(); - EqtTrace.Info("Closing the connection"); - } - - /// - public void Dispose() - { - if (this.channel != null) - { - this.channel.MessageReceived -= this.onMessageReceived; - } - - this.communicationServer.Stop(); - } - - private void OnExecutionMessageReceived(object sender, MessageReceivedEventArgs messageReceived, ITestRunEventsHandler testRunEventsHandler) - { - try - { - var rawMessage = messageReceived.Data; - - // Send raw message first to unblock handlers waiting to send message to IDEs - testRunEventsHandler.HandleRawMessage(rawMessage); - - var message = this.dataSerializer.DeserializeMessage(rawMessage); - switch (message.MessageType) - { - case MessageType.TestRunStatsChange: - var testRunChangedArgs = this.dataSerializer.DeserializePayload(message); - testRunEventsHandler.HandleTestRunStatsChange(testRunChangedArgs); - break; - case MessageType.ExecutionComplete: - var testRunCompletePayload = this.dataSerializer.DeserializePayload(message); - - testRunEventsHandler.HandleTestRunComplete( - testRunCompletePayload.TestRunCompleteArgs, - testRunCompletePayload.LastRunTests, - testRunCompletePayload.RunAttachments, - testRunCompletePayload.ExecutorUris); - - this.SetOperationComplete(); - break; - case MessageType.TestMessage: - var testMessagePayload = this.dataSerializer.DeserializePayload(message); - testRunEventsHandler.HandleLogMessage(testMessagePayload.MessageLevel, testMessagePayload.Message); - break; - case MessageType.LaunchAdapterProcessWithDebuggerAttached: - var testProcessStartInfo = this.dataSerializer.DeserializePayload(message); - int processId = testRunEventsHandler.LaunchProcessWithDebuggerAttached(testProcessStartInfo); - - var data = - this.dataSerializer.SerializePayload( - MessageType.LaunchAdapterProcessWithDebuggerAttachedCallback, - processId, - this.protocolVersion); - - this.channel.Send(data); - break; - } - } - catch (Exception exception) - { - this.OnTestRunAbort(testRunEventsHandler, exception, false); - } - } - - private void OnTestRunAbort(ITestRunEventsHandler testRunEventsHandler, Exception exception, bool getClientError) - { - if (this.IsOperationComplete()) - { - EqtTrace.Verbose("TestRequestSender: OnTestRunAbort: Operation is already complete. Skip error message."); - return; - } - - EqtTrace.Verbose("TestRequestSender: OnTestRunAbort: Set operation complete."); - this.SetOperationComplete(); - - var reason = this.GetAbortErrorMessage(exception, getClientError); - EqtTrace.Error("TestRequestSender: Aborting test run because {0}", reason); - this.LogErrorMessage(string.Format(CommonResources.AbortedTestRun, reason)); - - // notify test run abort to vstest console wrapper. - var completeArgs = new TestRunCompleteEventArgs(null, false, true, exception, null, TimeSpan.Zero); - var payload = new TestRunCompletePayload { TestRunCompleteArgs = completeArgs }; - var rawMessage = this.dataSerializer.SerializePayload(MessageType.ExecutionComplete, payload); - testRunEventsHandler.HandleRawMessage(rawMessage); - - // notify of a test run complete and bail out. - testRunEventsHandler.HandleTestRunComplete(completeArgs, null, null, null); - } - - private void OnDiscoveryAbort(ITestDiscoveryEventsHandler2 eventHandler, Exception exception, bool getClientError) - { - if (this.IsOperationComplete()) - { - EqtTrace.Verbose("TestRequestSender: OnDiscoveryAbort: Operation is already complete. Skip error message."); - return; - } - - EqtTrace.Verbose("TestRequestSender: OnDiscoveryAbort: Set operation complete."); - this.SetOperationComplete(); - - var reason = this.GetAbortErrorMessage(exception, getClientError); - EqtTrace.Error("TestRequestSender: Aborting test discovery because {0}", reason); - this.LogErrorMessage(string.Format(CommonResources.AbortedTestDiscovery, reason)); - - // Notify discovery abort to IDE test output - var payload = new DiscoveryCompletePayload() - { - IsAborted = true, - LastDiscoveredTests = null, - TotalTests = -1 - }; - var rawMessage = this.dataSerializer.SerializePayload(MessageType.DiscoveryComplete, payload); - eventHandler.HandleRawMessage(rawMessage); - - // Complete discovery - var discoveryCompleteEventsArgs = new DiscoveryCompleteEventArgs(-1, true); - - eventHandler.HandleDiscoveryComplete(discoveryCompleteEventsArgs, null); - } - - private string GetAbortErrorMessage(Exception exception, bool getClientError) - { - EqtTrace.Verbose("TestRequestSender: GetAbortErrorMessage: Exception: " + exception); - - // It is also possible for an operation to abort even if client has not - // disconnected, e.g. if there's an error parsing the response from test host. We - // want the exception to be available in those scenarios. - var reason = exception?.Message; - if (getClientError) - { - EqtTrace.Verbose("TestRequestSender: GetAbortErrorMessage: Client has disconnected. Wait for standard error."); - - // Set a default message and wait for test host to exit for a moment - reason = CommonResources.UnableToCommunicateToTestHost; - if (this.clientExited.Wait(this.clientExitedWaitTime)) - { - EqtTrace.Info("TestRequestSender: GetAbortErrorMessage: Received test host error message."); - reason = this.clientExitErrorMessage; - } - - EqtTrace.Info("TestRequestSender: GetAbortErrorMessage: Timed out waiting for test host error message."); - } - - return reason; - } - - private void LogErrorMessage(string message) - { - if (this.messageEventHandler == null) - { - EqtTrace.Error("TestRequestSender.LogErrorMessage: Message event handler not set. Error: " + message); - return; - } - - // Log to vstest console - this.messageEventHandler.HandleLogMessage(TestMessageLevel.Error, message); - - // Log to vs ide test output - var testMessagePayload = new TestMessagePayload { MessageLevel = TestMessageLevel.Error, Message = message }; - var rawMessage = this.dataSerializer.SerializePayload(MessageType.TestMessage, testMessagePayload); - this.messageEventHandler.HandleRawMessage(rawMessage); - } - - private bool IsOperationComplete() - { - return this.operationCompleted == 1; - } - - private void SetOperationComplete() - { - // Complete the currently ongoing operation (Discovery/Execution) - Interlocked.CompareExchange(ref this.operationCompleted, 1, 0); - } - } -} diff --git a/src/Microsoft.TestPlatform.CoreUtilities/Utilities/StringExtensions.cs b/src/Microsoft.TestPlatform.CoreUtilities/Utilities/StringExtensions.cs index 1e04c64a9b..f7a618c5fe 100644 --- a/src/Microsoft.TestPlatform.CoreUtilities/Utilities/StringExtensions.cs +++ b/src/Microsoft.TestPlatform.CoreUtilities/Utilities/StringExtensions.cs @@ -3,6 +3,9 @@ namespace Microsoft.VisualStudio.TestPlatform.CoreUtilities.Extensions { + using System; + using System.Net; + public static class StringExtensions { /// diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Client/Parallel/ParallelProxyExecutionManager.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/Client/Parallel/ParallelProxyExecutionManager.cs index dfa9d73f64..b1c889af95 100644 --- a/src/Microsoft.TestPlatform.CrossPlatEngine/Client/Parallel/ParallelProxyExecutionManager.cs +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Client/Parallel/ParallelProxyExecutionManager.cs @@ -197,22 +197,17 @@ public bool HandlePartialRunComplete( return true; } - // In case of DataCollection we only start dc.exe on initialize, & close once the TestRun is complete, - // So instead of resuing ProxyExecutionManager, we will close it here, & create new PEMWDC - // In Case of Abort, clean old one and create new proxyExecutionManager in place of old one. - if (!this.SharedHosts || testRunCompleteArgs.IsAborted || (proxyExecutionManager is ProxyExecutionManagerWithDataCollection)) - { - if (EqtTrace.IsVerboseEnabled) - { - EqtTrace.Verbose("ParallelProxyExecutionManager: HandlePartialRunComplete: Replace execution manager. Shared: {0}, Aborted: {1}.", this.SharedHosts, testRunCompleteArgs.IsAborted); - } - this.RemoveManager(proxyExecutionManager); - proxyExecutionManager = CreateNewConcurrentManager(); - var parallelEventsHandler = this.GetEventsHandler(proxyExecutionManager); - this.AddManager(proxyExecutionManager, parallelEventsHandler); + if (EqtTrace.IsVerboseEnabled) + { + EqtTrace.Verbose("ParallelProxyExecutionManager: HandlePartialRunComplete: Replace execution manager. Shared: {0}, Aborted: {1}.", this.SharedHosts, testRunCompleteArgs.IsAborted); } + this.RemoveManager(proxyExecutionManager); + proxyExecutionManager = CreateNewConcurrentManager(); + var parallelEventsHandler = this.GetEventsHandler(proxyExecutionManager); + this.AddManager(proxyExecutionManager, parallelEventsHandler); + // If cancel is triggered for any one run or abort is requested by test platform, there is no reason to fetch next source // and queue another test run if (!testRunCompleteArgs.IsCanceled && !abortRequested) diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/EventHandlers/TestRequestHandler.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/EventHandlers/TestRequestHandler.cs index f5a36bcb4e..7989de7478 100644 --- a/src/Microsoft.TestPlatform.CrossPlatEngine/EventHandlers/TestRequestHandler.cs +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/EventHandlers/TestRequestHandler.cs @@ -19,281 +19,178 @@ namespace Microsoft.VisualStudio.TestPlatform.CommunicationUtilities using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Utilities; using Microsoft.VisualStudio.TestPlatform.Utilities; - using CrossPlatResources = Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Resources.Resources; + using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine; - /// - /// Utility class to fecilitate the IPC comunication. Acts as Client. - /// public class TestRequestHandler : IDisposable, ITestRequestHandler { - private ICommunicationManager communicationManager; + private readonly IDataSerializer dataSerializer; + private ITestHostManagerFactory testHostManagerFactory; + private ICommunicationEndPoint communicationEndPoint; + private int protocolVersion = 1; - private ITransport transport; + private TestHostConnectionInfo connectionInfo; - private IDataSerializer dataSerializer; + private int highestSupportedVersion = 2; + private JobQueue jobQueue; + private ICommunicationChannel channel; - private Action onAckMessageRecieved; + private ManualResetEventSlim requestSenderConnected; + private ManualResetEventSlim testHostManagerFactoryReady; - private int highestSupportedVersion = 2; + private ManualResetEventSlim sessionCompleted; - // Set default to 1, if protocol version check does not happen - // that implies runner is using version 1 - private int protocolVersion = 1; + private Action onAckMessageRecieved; - public TestRequestHandler(TestHostConnectionInfo connectionInfo) - : this(new SocketCommunicationManager(), connectionInfo, JsonDataSerializer.Instance) + /// + /// Initializes a new instance of the . + /// + public TestRequestHandler(TestHostConnectionInfo connectionInfo) : this(connectionInfo, JsonDataSerializer.Instance) { } - internal TestRequestHandler(ICommunicationManager communicationManager, TestHostConnectionInfo connectionInfo, IDataSerializer dataSerializer) + protected TestRequestHandler(TestHostConnectionInfo connectionInfo, ICommunicationEndPoint communicationEndpoint, IDataSerializer dataSerializer, JobQueue jobQueue, Action onAckMessageRecieved) { - this.communicationManager = communicationManager; - this.transport = new SocketTransport(communicationManager, connectionInfo); + this.communicationEndPoint = communicationEndpoint; + this.connectionInfo = connectionInfo; this.dataSerializer = dataSerializer; + this.requestSenderConnected = new ManualResetEventSlim(false); + this.testHostManagerFactoryReady = new ManualResetEventSlim(false); + this.sessionCompleted = new ManualResetEventSlim(false); + this.onAckMessageRecieved = onAckMessageRecieved; + this.jobQueue = jobQueue; } - /// - public void InitializeCommunication() + protected TestRequestHandler(TestHostConnectionInfo connectionInfo, IDataSerializer dataSerializer) { - this.transport.Initialize(); - } - - /// - public bool WaitForRequestSenderConnection(int connectionTimeout) - { - return this.transport.WaitForConnection(connectionTimeout); - } + this.connectionInfo = connectionInfo; + this.dataSerializer = dataSerializer; + this.requestSenderConnected = new ManualResetEventSlim(false); + this.sessionCompleted = new ManualResetEventSlim(false); + this.testHostManagerFactoryReady = new ManualResetEventSlim(false); + this.onAckMessageRecieved = (message) => { throw new NotImplementedException(); }; - /// - /// Listens to the commands from server - /// - /// the test host manager. - public void ProcessRequests(ITestHostManagerFactory testHostManagerFactory) - { - bool isSessionEnd = false; + this.SetCommunicationEndPoint(); - var jobQueue = new JobQueue( - action => { action(); }, + this.jobQueue = new JobQueue( + (action) => { action(); }, "TestHostOperationQueue", 500, 25000000, true, (message) => EqtTrace.Error(message)); + } - do + /// + public virtual void InitializeCommunication() + { + this.communicationEndPoint.Connected += (sender, connectedArgs) => { - var message = this.communicationManager.ReceiveMessage(); - if (EqtTrace.IsInfoEnabled) + if (!connectedArgs.Connected) { - EqtTrace.Info("TestRequestHandler.ProcessRequests: received message: {0}", message); + requestSenderConnected.Set(); + throw connectedArgs.Fault; } + this.channel = connectedArgs.Channel; + this.channel.MessageReceived += this.OnMessageReceived; + requestSenderConnected.Set(); + }; - switch (message.MessageType) - { - case MessageType.VersionCheck: - var version = this.dataSerializer.DeserializePayload(message); - this.protocolVersion = Math.Min(version, highestSupportedVersion); - this.communicationManager.SendMessage(MessageType.VersionCheck, this.protocolVersion); - - // Can only do this after InitializeCommunication because TestHost cannot "Send Log" unless communications are initialized - if (!string.IsNullOrEmpty(EqtTrace.LogFile)) - { - this.SendLog(TestMessageLevel.Informational, string.Format(CrossPlatResources.TesthostDiagLogOutputFile, EqtTrace.LogFile)); - } - else if (!string.IsNullOrEmpty(EqtTrace.ErrorOnInitialization)) - { - this.SendLog(TestMessageLevel.Warning, EqtTrace.ErrorOnInitialization); - } - - break; - - case MessageType.DiscoveryInitialize: - { - EqtTrace.Info("Discovery Session Initialize."); - var pathToAdditionalExtensions = this.dataSerializer.DeserializePayload>(message); - jobQueue.QueueJob( - () => testHostManagerFactory.GetDiscoveryManager() - .Initialize(pathToAdditionalExtensions), - 0); - break; - } - - case MessageType.StartDiscovery: - { - EqtTrace.Info("Discovery started."); - - var discoveryEventsHandler = new TestDiscoveryEventHandler(this); - var discoveryCriteria = this.dataSerializer.DeserializePayload(message); - jobQueue.QueueJob( - () => testHostManagerFactory.GetDiscoveryManager() - .DiscoverTests(discoveryCriteria, discoveryEventsHandler), - 0); - - break; - } - - case MessageType.ExecutionInitialize: - { - EqtTrace.Info("Discovery Session Initialize."); - var pathToAdditionalExtensions = this.dataSerializer.DeserializePayload>(message); - jobQueue.QueueJob( - () => testHostManagerFactory.GetExecutionManager() - .Initialize(pathToAdditionalExtensions), - 0); - break; - } - - case MessageType.StartTestExecutionWithSources: - { - EqtTrace.Info("Execution started."); - var testRunEventsHandler = new TestRunEventsHandler(this); - - var testRunCriteriaWithSources = this.dataSerializer.DeserializePayload(message); - jobQueue.QueueJob( - () => - testHostManagerFactory.GetExecutionManager() - .StartTestRun( - testRunCriteriaWithSources.AdapterSourceMap, - testRunCriteriaWithSources.Package, - testRunCriteriaWithSources.RunSettings, - testRunCriteriaWithSources.TestExecutionContext, - this.GetTestCaseEventsHandler(testRunCriteriaWithSources.RunSettings), - testRunEventsHandler), - 0); - - break; - } - - case MessageType.StartTestExecutionWithTests: - { - EqtTrace.Info("Execution started."); - var testRunEventsHandler = new TestRunEventsHandler(this); - - var testRunCriteriaWithTests = - this.communicationManager.DeserializePayload(message); - - jobQueue.QueueJob( - () => - testHostManagerFactory.GetExecutionManager() - .StartTestRun( - testRunCriteriaWithTests.Tests, - testRunCriteriaWithTests.Package, - testRunCriteriaWithTests.RunSettings, - testRunCriteriaWithTests.TestExecutionContext, - this.GetTestCaseEventsHandler(testRunCriteriaWithTests.RunSettings), - testRunEventsHandler), - 0); - - break; - } - - case MessageType.CancelTestRun: - jobQueue.Pause(); - testHostManagerFactory.GetExecutionManager().Cancel(); - break; - - case MessageType.LaunchAdapterProcessWithDebuggerAttachedCallback: - this.onAckMessageRecieved?.Invoke(message); - break; + this.communicationEndPoint.Start(connectionInfo.Endpoint); + } - case MessageType.AbortTestRun: - jobQueue.Pause(); - testHostManagerFactory.GetExecutionManager().Abort(); - break; + /// + public bool WaitForRequestSenderConnection(int connectionTimeout) + { + return requestSenderConnected.Wait(connectionTimeout); + } - case MessageType.SessionEnd: - { - EqtTrace.Info("Session End message received from server. Closing the connection."); - isSessionEnd = true; - this.Close(); - break; - } - - case MessageType.SessionAbort: - { - // Dont do anything for now. - break; - } - - default: - { - EqtTrace.Info("Invalid Message types"); - break; - } - } - } - while (!isSessionEnd); + /// + public void ProcessRequests(ITestHostManagerFactory testHostManagerFactory) + { + this.testHostManagerFactory = testHostManagerFactory; + this.testHostManagerFactoryReady.Set(); + this.sessionCompleted.Wait(); } - /// + /// public void Dispose() { - this.transport.Dispose(); + this.communicationEndPoint.Stop(); + this.channel?.Dispose(); } - /// + /// public void Close() { this.Dispose(); EqtTrace.Info("Closing the connection !"); } - /// + /// public void SendTestCases(IEnumerable discoveredTestCases) { - this.communicationManager.SendMessage(MessageType.TestCasesFound, discoveredTestCases, this.protocolVersion); + var data = this.dataSerializer.SerializePayload(MessageType.TestCasesFound, discoveredTestCases, this.protocolVersion); + this.channel.Send(data); } - /// + /// public void SendTestRunStatistics(TestRunChangedEventArgs testRunChangedArgs) { - this.communicationManager.SendMessage(MessageType.TestRunStatsChange, testRunChangedArgs, this.protocolVersion); + var data = this.dataSerializer.SerializePayload(MessageType.TestRunStatsChange, testRunChangedArgs, this.protocolVersion); + this.channel.Send(data); } - /// + /// public void SendLog(TestMessageLevel messageLevel, string message) { - var testMessagePayload = new TestMessagePayload { MessageLevel = messageLevel, Message = message }; - this.communicationManager.SendMessage(MessageType.TestMessage, testMessagePayload, this.protocolVersion); + var data = this.dataSerializer.SerializePayload( + MessageType.TestMessage, + new TestMessagePayload { MessageLevel = messageLevel, Message = message }, + this.protocolVersion); + this.channel.Send(data); } - /// + /// public void SendExecutionComplete( - TestRunCompleteEventArgs testRunCompleteArgs, - TestRunChangedEventArgs lastChunkArgs, - ICollection runContextAttachments, - ICollection executorUris) + TestRunCompleteEventArgs testRunCompleteArgs, + TestRunChangedEventArgs lastChunkArgs, + ICollection runContextAttachments, + ICollection executorUris) { - var payload = new TestRunCompletePayload - { - TestRunCompleteArgs = testRunCompleteArgs, - LastRunTests = lastChunkArgs, - RunAttachments = runContextAttachments, - ExecutorUris = executorUris - }; - - this.communicationManager.SendMessage(MessageType.ExecutionComplete, payload, this.protocolVersion); + var data = this.dataSerializer.SerializePayload( + MessageType.ExecutionComplete, + new TestRunCompletePayload + { + TestRunCompleteArgs = testRunCompleteArgs, + LastRunTests = lastChunkArgs, + RunAttachments = runContextAttachments, + ExecutorUris = executorUris + }, + this.protocolVersion); + this.channel.Send(data); } - /// + /// public void DiscoveryComplete(DiscoveryCompleteEventArgs discoveryCompleteEventArgs, IEnumerable lastChunk) { - var discoveryCompletePayload = new DiscoveryCompletePayload - { - TotalTests = discoveryCompleteEventArgs.TotalCount, - LastDiscoveredTests = discoveryCompleteEventArgs.IsAborted ? null : lastChunk, - IsAborted = discoveryCompleteEventArgs.IsAborted, - Metrics = discoveryCompleteEventArgs.Metrics - }; - - this.communicationManager.SendMessage(MessageType.DiscoveryComplete, discoveryCompletePayload, this.protocolVersion); + var data = this.dataSerializer.SerializePayload( + MessageType.DiscoveryComplete, + new DiscoveryCompletePayload + { + TotalTests = discoveryCompleteEventArgs.TotalCount, + LastDiscoveredTests = discoveryCompleteEventArgs.IsAborted ? null : lastChunk, + IsAborted = discoveryCompleteEventArgs.IsAborted, + Metrics = discoveryCompleteEventArgs.Metrics + }, + this.protocolVersion); + this.channel.Send(data); } - /// + /// public int LaunchProcessWithDebuggerAttached(TestProcessStartInfo testProcessStartInfo) { - var waitHandle = new AutoResetEvent(false); + var waitHandle = new ManualResetEventSlim(false); Message ackMessage = null; this.onAckMessageRecieved = (ackRawMessage) => { @@ -301,24 +198,196 @@ public int LaunchProcessWithDebuggerAttached(TestProcessStartInfo testProcessSta waitHandle.Set(); }; - this.communicationManager.SendMessage(MessageType.LaunchAdapterProcessWithDebuggerAttached, testProcessStartInfo, this.protocolVersion); + var data = dataSerializer.SerializePayload(MessageType.LaunchAdapterProcessWithDebuggerAttached, + testProcessStartInfo, protocolVersion); + + this.channel.Send(data); - waitHandle.WaitOne(); + EqtTrace.Verbose("Waiting for LaunchAdapterProcessWithDebuggerAttached ack"); + waitHandle.Wait(); this.onAckMessageRecieved = null; return this.dataSerializer.DeserializePayload(ackMessage); } + public void OnMessageReceived(object sender, MessageReceivedEventArgs messageReceivedArgs) + { + var message = this.dataSerializer.DeserializeMessage(messageReceivedArgs.Data); + + if (EqtTrace.IsInfoEnabled) + { + EqtTrace.Info("TestRequestHandler.ProcessRequests: received message: {0}", message); + } + + switch (message.MessageType) + { + case MessageType.VersionCheck: + var version = this.dataSerializer.DeserializePayload(message); + this.protocolVersion = Math.Min(version, highestSupportedVersion); + + // Send the negotiated protocol to request sender + this.channel.Send(this.dataSerializer.SerializePayload(MessageType.VersionCheck, this.protocolVersion)); + + // Can only do this after InitializeCommunication because TestHost cannot "Send Log" unless communications are initialized + if (!string.IsNullOrEmpty(EqtTrace.LogFile)) + { + this.SendLog(TestMessageLevel.Informational, string.Format(CrossPlatResources.TesthostDiagLogOutputFile, EqtTrace.LogFile)); + } + else if (!string.IsNullOrEmpty(EqtTrace.ErrorOnInitialization)) + { + this.SendLog(TestMessageLevel.Warning, EqtTrace.ErrorOnInitialization); + } + break; + + case MessageType.DiscoveryInitialize: + { + EqtTrace.Info("Discovery Session Initialize."); + this.testHostManagerFactoryReady.Wait(); + var pathToAdditionalExtensions = message.Payload.ToObject>(); + jobQueue.QueueJob( + () => + testHostManagerFactory.GetDiscoveryManager().Initialize(pathToAdditionalExtensions), 0); + break; + } + + case MessageType.StartDiscovery: + { + EqtTrace.Info("Discovery started."); + this.testHostManagerFactoryReady.Wait(); + var discoveryEventsHandler = new TestDiscoveryEventHandler(this); + var discoveryCriteria = message.Payload.ToObject(); + jobQueue.QueueJob( + () => + testHostManagerFactory.GetDiscoveryManager() + .DiscoverTests(discoveryCriteria, discoveryEventsHandler), 0); + + break; + } + + case MessageType.ExecutionInitialize: + { + EqtTrace.Info("Discovery Session Initialize."); + this.testHostManagerFactoryReady.Wait(); + var pathToAdditionalExtensions = message.Payload.ToObject>(); + jobQueue.QueueJob( + () => + testHostManagerFactory.GetExecutionManager().Initialize(pathToAdditionalExtensions), 0); + break; + } + + case MessageType.StartTestExecutionWithSources: + { + EqtTrace.Info("Execution started."); + var testRunEventsHandler = new TestRunEventsHandler(this); + this.testHostManagerFactoryReady.Wait(); + var testRunCriteriaWithSources = message.Payload.ToObject(); + jobQueue.QueueJob( + () => + testHostManagerFactory.GetExecutionManager() + .StartTestRun( + testRunCriteriaWithSources.AdapterSourceMap, + testRunCriteriaWithSources.Package, + testRunCriteriaWithSources.RunSettings, + testRunCriteriaWithSources.TestExecutionContext, + this.GetTestCaseEventsHandler(testRunCriteriaWithSources.RunSettings), + testRunEventsHandler), + 0); + + break; + } + + case MessageType.StartTestExecutionWithTests: + { + EqtTrace.Info("Execution started."); + var testRunEventsHandler = new TestRunEventsHandler(this); + this.testHostManagerFactoryReady.Wait(); + var testRunCriteriaWithTests = + this.dataSerializer.DeserializePayload(message); + + jobQueue.QueueJob( + () => + testHostManagerFactory.GetExecutionManager() + .StartTestRun( + testRunCriteriaWithTests.Tests, + testRunCriteriaWithTests.Package, + testRunCriteriaWithTests.RunSettings, + testRunCriteriaWithTests.TestExecutionContext, + this.GetTestCaseEventsHandler(testRunCriteriaWithTests.RunSettings), + testRunEventsHandler), + 0); + + break; + } + + case MessageType.CancelTestRun: + jobQueue.Pause(); + this.testHostManagerFactoryReady.Wait(); + testHostManagerFactory.GetExecutionManager().Cancel(); + break; + + case MessageType.LaunchAdapterProcessWithDebuggerAttachedCallback: + this.onAckMessageRecieved?.Invoke(message); + break; + + case MessageType.AbortTestRun: + jobQueue.Pause(); + this.testHostManagerFactoryReady.Wait(); + testHostManagerFactory.GetExecutionManager().Abort(); + break; + + case MessageType.SessionEnd: + { + EqtTrace.Info("Session End message received from server. Closing the connection."); + sessionCompleted.Set(); + this.Close(); + break; + } + + case MessageType.SessionAbort: + { + // Dont do anything for now. + break; + } + + default: + { + EqtTrace.Info("Invalid Message types"); + break; + } + } + } + private ITestCaseEventsHandler GetTestCaseEventsHandler(string runSettings) { ITestCaseEventsHandler testCaseEventsHandler = null; + // Listen to test case events only if data collection is enabled if ((XmlRunSettingsUtilities.IsDataCollectionEnabled(runSettings) && DataCollectionTestCaseEventSender.Instance != null) || XmlRunSettingsUtilities.IsInProcDataCollectionEnabled(runSettings)) { testCaseEventsHandler = new TestCaseEventsHandler(); - var testEventsPublisher = testCaseEventsHandler as ITestEventsPublisher; } return testCaseEventsHandler; } + + private void SetCommunicationEndPoint() + { + if (this.connectionInfo.Role == ConnectionRole.Host) + { + this.communicationEndPoint = new SocketServer(); + if (EqtTrace.IsVerboseEnabled) + { + EqtTrace.Verbose("TestRequestHanlder is acting as server"); + } + } + else + { + this.communicationEndPoint = new SocketClient(); + if (EqtTrace.IsVerboseEnabled) + { + EqtTrace.Verbose("TestRequestHanlder is acting as client"); + } + } + } + } -} \ No newline at end of file +} diff --git a/test/Microsoft.TestPlatform.Common.UnitTests/Microsoft.TestPlatform.Common.UnitTests.csproj b/test/Microsoft.TestPlatform.Common.UnitTests/Microsoft.TestPlatform.Common.UnitTests.csproj index 038790082b..0933c29489 100644 --- a/test/Microsoft.TestPlatform.Common.UnitTests/Microsoft.TestPlatform.Common.UnitTests.csproj +++ b/test/Microsoft.TestPlatform.Common.UnitTests/Microsoft.TestPlatform.Common.UnitTests.csproj @@ -12,17 +12,6 @@ - - true - - - true - - - - true - - diff --git a/test/Microsoft.TestPlatform.CommunicationUtilities.PlatformTests/SocketClientTests.cs b/test/Microsoft.TestPlatform.CommunicationUtilities.PlatformTests/SocketClientTests.cs index 5386451deb..8b450ef6c7 100644 --- a/test/Microsoft.TestPlatform.CommunicationUtilities.PlatformTests/SocketClientTests.cs +++ b/test/Microsoft.TestPlatform.CommunicationUtilities.PlatformTests/SocketClientTests.cs @@ -18,7 +18,7 @@ public class SocketClientTests : SocketTestsBase, IDisposable { private readonly TcpListener tcpListener; - private readonly ICommunicationClient socketClient; + private readonly ICommunicationEndPoint socketClient; private TcpClient tcpClient; @@ -139,7 +139,7 @@ protected override ICommunicationChannel SetupChannel(out ConnectedEventArgs con ICommunicationChannel channel = null; ConnectedEventArgs serverConnectedEvent = null; ManualResetEvent waitEvent = new ManualResetEvent(false); - this.socketClient.ServerConnected += (sender, eventArgs) => + this.socketClient.Connected += (sender, eventArgs) => { serverConnectedEvent = eventArgs; channel = eventArgs.Channel; @@ -163,8 +163,11 @@ protected override ICommunicationChannel SetupChannel(out ConnectedEventArgs con private ManualResetEvent SetupClientDisconnect(out ICommunicationChannel channel) { var waitEvent = new ManualResetEvent(false); - this.socketClient.ServerDisconnected += (s, e) => { waitEvent.Set(); }; + this.socketClient.Disconnected += (s, e) => { waitEvent.Set(); }; channel = this.SetupChannel(out ConnectedEventArgs _); + channel.MessageReceived += (sender, args) => + { + }; return waitEvent; } @@ -172,7 +175,7 @@ private string StartLocalServer() { this.tcpListener.Start(); - return ((IPEndPoint)this.tcpListener.LocalEndpoint).Port.ToString(); + return ((IPEndPoint)this.tcpListener.LocalEndpoint).ToString(); } } } diff --git a/test/Microsoft.TestPlatform.CommunicationUtilities.PlatformTests/SocketServerTests.cs b/test/Microsoft.TestPlatform.CommunicationUtilities.PlatformTests/SocketServerTests.cs index eb3e463f61..69cc154485 100644 --- a/test/Microsoft.TestPlatform.CommunicationUtilities.PlatformTests/SocketServerTests.cs +++ b/test/Microsoft.TestPlatform.CommunicationUtilities.PlatformTests/SocketServerTests.cs @@ -9,7 +9,6 @@ namespace Microsoft.TestPlatform.CommunicationUtilities.PlatformTests using System.Net.Sockets; using System.Threading; using System.Threading.Tasks; - using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities; using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.Interfaces; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -18,8 +17,8 @@ namespace Microsoft.TestPlatform.CommunicationUtilities.PlatformTests public class SocketServerTests : SocketTestsBase, IDisposable { private readonly TcpClient tcpClient; - - private readonly ICommunicationServer socketServer; + private readonly string defaultConnection = IPAddress.Loopback.ToString() + ":0"; + private readonly ICommunicationEndPoint socketServer; public SocketServerTests() { @@ -45,17 +44,17 @@ public void Dispose() [TestMethod] public async Task SocketServerStartShouldHostServer() { - var connectionInfo = this.socketServer.Start(); + var connectionInfo = this.socketServer.Start(this.defaultConnection); Assert.IsFalse(string.IsNullOrEmpty(connectionInfo)); - await this.ConnectToServer(int.Parse(connectionInfo)); + await this.ConnectToServer(connectionInfo.GetIPEndPoint().Port); Assert.IsTrue(this.tcpClient.Connected); } [TestMethod] public void SocketServerStopShouldStopListening() { - var connectionInfo = this.socketServer.Start(); + var connectionInfo = this.socketServer.Start(this.defaultConnection); this.socketServer.Stop(); @@ -63,7 +62,7 @@ public void SocketServerStopShouldStopListening() { // This method throws ExtendedSocketException (which is private). It is not possible // to use Assert.ThrowsException in this case. - this.ConnectToServer(int.Parse(connectionInfo)).GetAwaiter().GetResult(); + this.ConnectToServer(connectionInfo.GetIPEndPoint().Port).GetAwaiter().GetResult(); } catch (SocketException) { @@ -74,7 +73,7 @@ public void SocketServerStopShouldStopListening() public void SocketServerStopShouldCloseClient() { ManualResetEvent waitEvent = new ManualResetEvent(false); - this.socketServer.ClientDisconnected += (s, e) => { waitEvent.Set(); }; + this.socketServer.Disconnected += (s, e) => { waitEvent.Set(); }; this.SetupChannel(out ConnectedEventArgs clientConnected); this.socketServer.Stop(); @@ -88,7 +87,7 @@ public void SocketServerStopShouldRaiseClientDisconnectedEventOnClientDisconnect { DisconnectedEventArgs disconnected = null; ManualResetEvent waitEvent = new ManualResetEvent(false); - this.socketServer.ClientDisconnected += (s, e) => + this.socketServer.Disconnected += (s, e) => { disconnected = e; waitEvent.Set(); @@ -105,13 +104,13 @@ public void SocketServerStopShouldRaiseClientDisconnectedEventOnClientDisconnect [TestMethod] public void SocketServerStopShouldCloseChannel() { - ManualResetEvent waitEvent = new ManualResetEvent(false); + var waitEvent = new ManualResetEventSlim(false); var channel = this.SetupChannel(out ConnectedEventArgs clientConnected); - this.socketServer.ClientDisconnected += (s, e) => { waitEvent.Set(); }; + this.socketServer.Disconnected += (s, e) => { waitEvent.Set(); }; this.socketServer.Stop(); - waitEvent.WaitOne(); + waitEvent.Wait(); Assert.ThrowsException(() => channel.Send(DUMMYDATA)); } @@ -120,13 +119,17 @@ public void SocketServerShouldRaiseClientDisconnectedEventIfConnectionIsBroken() { DisconnectedEventArgs clientDisconnected = null; ManualResetEvent waitEvent = new ManualResetEvent(false); - this.socketServer.ClientDisconnected += (sender, eventArgs) => + this.socketServer.Disconnected += (sender, eventArgs) => { clientDisconnected = eventArgs; waitEvent.Set(); }; var channel = this.SetupChannel(out ConnectedEventArgs clientConnected); + channel.MessageReceived += (sender, args) => + { + }; + // Close the client channel. Message loop should stop. #if NET451 // tcpClient.Close() calls tcpClient.Dispose(). @@ -139,20 +142,31 @@ public void SocketServerShouldRaiseClientDisconnectedEventIfConnectionIsBroken() Assert.IsTrue(clientDisconnected.Error is IOException); } + [TestMethod] + public async Task SocketEndpointShouldInitializeChannelOnServerConnection() + { + var channel = this.SetupChannel(out ConnectedEventArgs _); + + await channel.Send(DUMMYDATA); + + Assert.AreEqual(DUMMYDATA, ReadData(this.Client)); + } + protected override ICommunicationChannel SetupChannel(out ConnectedEventArgs connectedEvent) { ICommunicationChannel channel = null; ConnectedEventArgs clientConnectedEvent = null; ManualResetEvent waitEvent = new ManualResetEvent(false); - this.socketServer.ClientConnected += (sender, eventArgs) => + this.socketServer.Connected += (sender, eventArgs) => { clientConnectedEvent = eventArgs; channel = eventArgs.Channel; waitEvent.Set(); }; - var connectionInfo = this.socketServer.Start(); - this.ConnectToServer(int.Parse(connectionInfo)).GetAwaiter().GetResult(); + var connectionInfo = this.socketServer.Start(this.defaultConnection); + var port = connectionInfo.GetIPEndPoint().Port; + this.ConnectToServer(port).GetAwaiter().GetResult(); waitEvent.WaitOne(); connectedEvent = clientConnectedEvent; diff --git a/test/Microsoft.TestPlatform.CommunicationUtilities.PlatformTests/SocketTestsBase.cs b/test/Microsoft.TestPlatform.CommunicationUtilities.PlatformTests/SocketTestsBase.cs index 3efaed098c..e8ce03cf8f 100644 --- a/test/Microsoft.TestPlatform.CommunicationUtilities.PlatformTests/SocketTestsBase.cs +++ b/test/Microsoft.TestPlatform.CommunicationUtilities.PlatformTests/SocketTestsBase.cs @@ -3,10 +3,13 @@ namespace Microsoft.TestPlatform.CommunicationUtilities.PlatformTests { + using System; using System.IO; + using System.Net; using System.Net.Sockets; using System.Threading; using System.Threading.Tasks; + using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities; using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.Interfaces; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -26,16 +29,6 @@ public void SocketEndpointStartShouldRaiseServerConnectedEventOnServerConnection Assert.IsNotNull(connectedEventArgs); } - [TestMethod] - public async Task SocketEndpointShouldInitializeChannelOnServerConnection() - { - var channel = this.SetupChannel(out ConnectedEventArgs _); - - await channel.Send(DUMMYDATA); - - Assert.AreEqual(DUMMYDATA, ReadData(this.Client)); - } - [TestMethod] public void SocketEndpointShouldNotifyChannelOnDataAvailable() { diff --git a/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/LengthPrefixCommunicationChannelTests.cs b/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/LengthPrefixCommunicationChannelTests.cs index 0a5b69473e..57b7369216 100644 --- a/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/LengthPrefixCommunicationChannelTests.cs +++ b/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/LengthPrefixCommunicationChannelTests.cs @@ -110,7 +110,7 @@ public async Task MessageReceivedShouldProvideDataOverStream() } [TestMethod] - public async Task NotifyDataAvailableShouldReadStreamIfNoListenersAreRegistered() + public async Task NotifyDataAvailableShouldNotReadStreamIfNoListenersAreRegistered() { this.writer.Write(DUMMYDATA); SeekToBeginning(this.stream); @@ -119,7 +119,7 @@ public async Task NotifyDataAvailableShouldReadStreamIfNoListenersAreRegistered( // Data is read irrespective of listeners. See note in NotifyDataAvailable // implementation. - Assert.AreEqual(11, this.stream.Position); + Assert.AreEqual(0, this.stream.Position); } [TestMethod] diff --git a/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/TestRequestSenderTests.cs b/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/TestRequestSenderTests.cs index 0417cd5813..94dfe51409 100644 --- a/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/TestRequestSenderTests.cs +++ b/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/TestRequestSenderTests.cs @@ -1,10 +1,11 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. namespace Microsoft.TestPlatform.CommunicationUtilities.UnitTests { using System; using System.Collections.Generic; + using System.Linq; using System.Net; using System.Threading; using System.Threading.Tasks; @@ -22,617 +23,743 @@ namespace Microsoft.TestPlatform.CommunicationUtilities.UnitTests [TestClass] public class TestRequestSenderTests { + private const int DUMMYPROTOCOLVERSION = 42; + private const int DEFAULTPROTOCOLVERSION = 1; + private const int DUMMYNEGOTIATEDPROTOCOLVERSION = 41; + private const int CLIENTPROCESSEXITWAIT = 10 * 1000; + + private readonly Mock mockServer; + private readonly Mock mockDataSerializer; + private readonly Mock mockChannel; + + private readonly ConnectedEventArgs connectedEventArgs; + private readonly List pathToAdditionalExtensions = new List { "Hello", "World" }; + private readonly Mock mockDiscoveryEventsHandler; + private readonly Mock mockExecutionEventsHandler; + private readonly TestRunCriteriaWithSources testRunCriteriaWithSources; + private TestHostConnectionInfo connectionInfo; private ITestRequestSender testRequestSender; - private Mock mockCommunicationManager; - private Mock mockDataSerializer; private ProtocolConfig protocolConfig = new ProtocolConfig { Version = 2 }; - private TestHostConnectionInfo connectionInfo; - [TestInitialize] - public void TestInit() + public TestRequestSenderTests() { this.connectionInfo = new TestHostConnectionInfo { - Endpoint = IPAddress.Loopback + ":0", - Role = ConnectionRole.Client, - Transport = Transport.Sockets - }; - this.mockCommunicationManager = new Mock(); + Endpoint = IPAddress.Loopback + ":123", + Role = ConnectionRole.Client, + Transport = Transport.Sockets + }; + this.mockChannel = new Mock(); + this.mockServer = new Mock(); this.mockDataSerializer = new Mock(); - this.testRequestSender = new TestRequestSender(this.mockCommunicationManager.Object, this.connectionInfo, this.mockDataSerializer.Object, this.protocolConfig); - this.CheckAndSetProtocolVersion(); + this.testRequestSender = new TestableTestRequestSender(this.mockServer.Object, this.connectionInfo, this.mockDataSerializer.Object, new ProtocolConfig { Version = DUMMYPROTOCOLVERSION }); + + this.connectedEventArgs = new ConnectedEventArgs(this.mockChannel.Object); + this.mockDiscoveryEventsHandler = new Mock(); + this.mockExecutionEventsHandler = new Mock(); + this.testRunCriteriaWithSources = new TestRunCriteriaWithSources(new Dictionary>(), "runsettings", null, null); } [TestMethod] public void InitializeCommunicationShouldHostServerAndAcceptClient() { - this.mockCommunicationManager.Setup(mc => mc.HostServer(new IPEndPoint(IPAddress.Loopback, 0))).Returns(new IPEndPoint(IPAddress.Loopback, 123)); - - var port = this.testRequestSender.InitializeCommunication(); + var port = this.SetupFakeCommunicationChannel(); - this.mockCommunicationManager.Verify(mc => mc.HostServer(new IPEndPoint(IPAddress.Loopback, 0)), Times.Once); - this.mockCommunicationManager.Verify(mc => mc.AcceptClientAsync(), Times.Once); - Assert.AreEqual(port, 123, "Correct port must be returned."); + Assert.AreEqual(port, "123", "Correct port must be returned."); } [TestMethod] - public void InitializeCommunicationShouldSetUpClientIfTestRunnerIsClient() + public void WaitForRequestHandlerConnectionShouldWaitForClientToConnect() { - this.mockCommunicationManager.Setup(mc => mc.SetupClientAsync(new IPEndPoint(IPAddress.Loopback, 0))); + this.SetupFakeCommunicationChannel(); - // These settings are that of Test runtime(testhost) - this.connectionInfo = new TestHostConnectionInfo - { - Endpoint = IPAddress.Loopback + ":0", - Role = ConnectionRole.Host, - Transport = Transport.Sockets - }; - this.testRequestSender = new TestRequestSender(this.mockCommunicationManager.Object, this.connectionInfo, this.mockDataSerializer.Object, this.protocolConfig); - this.CheckAndSetProtocolVersion(); + var connected = this.testRequestSender.WaitForRequestHandlerConnection(1); + + Assert.IsTrue(connected); + } - this.testRequestSender.InitializeCommunication(); + [TestMethod] + public void CloseShouldCallStopServerOnCommunicationManager() + { + this.testRequestSender.Close(); - this.mockCommunicationManager.Verify(mc => mc.SetupClientAsync(new IPEndPoint(IPAddress.Loopback, 0)), Times.Once); + this.mockServer.Verify(mc => mc.Stop(), Times.Once); } [TestMethod] - public void WaitForRequestHandlerConnectionShouldCallWaitForClientConnection() + public void DisposeShouldCallStopServerOnCommunicationManager() { - this.testRequestSender.WaitForRequestHandlerConnection(123); + this.testRequestSender.Dispose(); - this.mockCommunicationManager.Verify(mc => mc.WaitForClientConnection(123), Times.Once); + this.mockServer.Verify(mc => mc.Stop(), Times.Once); } [TestMethod] - public void WaitForRequestHandlerConnectionShouldCallWaitForServerConnectionIfTestRunnerIsClient() + public void EndSessionShouldSendSessionEndMessage() { - // These settings are that of Test runtime(testhost) - this.connectionInfo = new TestHostConnectionInfo - { - Endpoint = IPAddress.Loopback + ":0", - Role = ConnectionRole.Host, - Transport = Transport.Sockets - }; + this.SetupFakeCommunicationChannel(); - this.testRequestSender = new TestRequestSender(this.mockCommunicationManager.Object, this.connectionInfo, this.mockDataSerializer.Object, this.protocolConfig); - this.CheckAndSetProtocolVersion(); + this.testRequestSender.EndSession(); - this.testRequestSender.WaitForRequestHandlerConnection(123); + this.mockDataSerializer.Verify(ds => ds.SerializeMessage(MessageType.SessionEnd), Times.Once); + this.mockChannel.Verify(c => c.Send(It.IsAny()), Times.Once); + } - this.mockCommunicationManager.Verify(mc => mc.WaitForServerConnection(123), Times.Once); + [TestMethod] + public void EndSessionShouldNotSendSessionEndMessageIfClientDisconnected() + { + this.SetupFakeCommunicationChannel(); + this.testRequestSender.DiscoverTests(new DiscoveryCriteria(), this.mockDiscoveryEventsHandler.Object); + this.RaiseClientDisconnectedEvent(); + + this.testRequestSender.EndSession(); + + this.mockDataSerializer.Verify(ds => ds.SerializeMessage(MessageType.SessionEnd), Times.Never); } [TestMethod] - public void CloseShouldCallStopServerOnCommunicationManager() + public void EndSessionShouldNotSendSessionEndMessageIfTestHostProcessExited() { - this.testRequestSender.Close(); + this.SetupFakeCommunicationChannel(); + this.testRequestSender.StartTestRun(this.testRunCriteriaWithSources, this.mockExecutionEventsHandler.Object); + this.testRequestSender.OnClientProcessExit("Dummy Message"); + + this.testRequestSender.EndSession(); - this.mockCommunicationManager.Verify(mc => mc.StopServer(), Times.Once); + this.mockDataSerializer.Verify(ds => ds.SerializeMessage(MessageType.SessionEnd), Times.Once); + } + + [DataTestMethod] + [DataRow("")] + [DataRow(" ")] + [DataRow(null)] + public void OnClientProcessExitShouldSendErrorMessageIfStdErrIsEmpty(string stderr) + { + this.SetupFakeCommunicationChannel(); + this.testRequestSender.DiscoverTests(new DiscoveryCriteria(), this.mockDiscoveryEventsHandler.Object); + + this.testRequestSender.OnClientProcessExit(stderr); + + var expectedErrorMessage = "Reason: " + stderr; + this.RaiseClientDisconnectedEvent(); + this.mockDiscoveryEventsHandler.Verify(eh => eh.HandleLogMessage(TestMessageLevel.Error, It.Is(s => s.EndsWith(expectedErrorMessage))), Times.Once); } [TestMethod] - public void DisposeShouldCallStopServerOnCommunicationManager() + public void OnClientProcessExitShouldNotSendErrorMessageIfOperationNotStarted() { - this.testRequestSender.Dispose(); + this.SetupFakeCommunicationChannel(); - this.mockCommunicationManager.Verify(mc => mc.StopServer(), Times.Once); + this.testRequestSender.OnClientProcessExit("Dummy Stderr"); + + this.RaiseClientDisconnectedEvent(); + this.mockDiscoveryEventsHandler.Verify(eh => eh.HandleLogMessage(TestMessageLevel.Error, It.Is(s => s.Contains("Dummy Stderr"))), Times.Never); } [TestMethod] - public void VersionCheckWithTestHostShouldCheckVersionIfVersionCheckPassesReturnTrue() + public void OnClientProcessExitShouldNotSendRawMessageIfOperationNotStarted() { - var mockCommunicationManager = new Mock(); - var message = new Message() { MessageType = MessageType.VersionCheck, Payload = this.protocolConfig.Version }; - mockCommunicationManager.Setup(mc => mc.ReceiveMessage()).Returns(message); - var testRequestSender = new TestRequestSender(mockCommunicationManager.Object, default(TestHostConnectionInfo), this.mockDataSerializer.Object, this.protocolConfig); + this.SetupFakeCommunicationChannel(); - testRequestSender.CheckVersionWithTestHost(); + this.testRequestSender.OnClientProcessExit("Dummy Stderr"); - mockCommunicationManager.Verify(mc => mc.SendMessage(MessageType.VersionCheck, this.protocolConfig.Version), Times.Once); + this.RaiseClientDisconnectedEvent(); + this.mockDataSerializer.Verify(ds => ds.SerializePayload(MessageType.TestMessage, It.Is(p => p.Message.Contains("Dummy Stderr"))), Times.Never); + this.mockExecutionEventsHandler.Verify(eh => eh.HandleRawMessage(It.IsAny()), Times.Never); } + #region Version Check Tests + [TestMethod] - public void VersionCheckWithTestHostShouldBeAbleToReceiveProtocolErrorAndThrowException() + public void CheckVersionWithTestHostShouldSendHighestSupportedVersion() { - var mockCommunicationManager = new Mock(); - var message = new Message() { MessageType = MessageType.ProtocolError, Payload = null }; - mockCommunicationManager.Setup(mc => mc.ReceiveMessage()).Returns(message); - var testRequestSender = new TestRequestSender(mockCommunicationManager.Object, default(TestHostConnectionInfo), this.mockDataSerializer.Object, this.protocolConfig); + this.SetupDeserializeMessage(MessageType.VersionCheck, 99); + this.SetupRaiseMessageReceivedOnCheckVersion(); + this.SetupFakeCommunicationChannel(); - var ex = Assert.ThrowsException(() => testRequestSender.CheckVersionWithTestHost()); + this.testRequestSender.CheckVersionWithTestHost(); - Assert.AreEqual("Protocol version check failed. Make sure test runner and host are compatible.", ex.Message); + this.mockDataSerializer.Verify(ds => ds.SerializePayload(MessageType.VersionCheck, DUMMYPROTOCOLVERSION), Times.Once); + this.mockChannel.Verify(mc => mc.Send(It.IsAny()), Times.Once); } [TestMethod] - public void VersionCheckWithTestHostForInvalidMessageShouldThrowException() + public void CheckVersionWithTestHostShouldThrowIfTestHostVersionDoesNotMatch() { - var mockCommunicationManager = new Mock(); - var message = new Message() { MessageType = MessageType.TestCasesFound, Payload = null }; - mockCommunicationManager.Setup(mc => mc.ReceiveMessage()).Returns(message); - var testRequestSender = new TestRequestSender(mockCommunicationManager.Object, default(TestHostConnectionInfo), this.mockDataSerializer.Object, this.protocolConfig); + this.SetupDeserializeMessage(MessageType.ProtocolError, string.Empty); + this.SetupRaiseMessageReceivedOnCheckVersion(); + this.SetupFakeCommunicationChannel(); + + Assert.ThrowsException(() => this.testRequestSender.CheckVersionWithTestHost()); + } - var ex = Assert.ThrowsException(() => testRequestSender.CheckVersionWithTestHost()); + [TestMethod] + public void CheckVersionWithTestHostShouldThrowIfUnexpectedResponseIsReceived() + { + this.SetupDeserializeMessage(MessageType.TestCasesFound, string.Empty); + this.SetupRaiseMessageReceivedOnCheckVersion(); + this.SetupFakeCommunicationChannel(); - Assert.AreEqual("Unexpected message received. Expected MessageType : ProtocolVersion Actual MessageType: TestDiscovery.TestFound", ex.Message); + Assert.ThrowsException(() => this.testRequestSender.CheckVersionWithTestHost()); } + #endregion + + #region Discovery Protocol Tests [TestMethod] public void InitializeDiscoveryShouldSendCommunicationMessageWithCorrectParameters() { - var paths = new List() { "Hello", "World" }; - this.CheckAndSetProtocolVersion(); - this.testRequestSender.InitializeDiscovery(paths); + this.SetupFakeCommunicationChannel(); - this.mockCommunicationManager.Verify(mc => mc.SendMessage(MessageType.DiscoveryInitialize, paths, this.protocolConfig.Version), Times.Once); + this.testRequestSender.InitializeDiscovery(this.pathToAdditionalExtensions); + + this.mockDataSerializer.Verify(d => d.SerializePayload(MessageType.DiscoveryInitialize, this.pathToAdditionalExtensions, 1), Times.Once); + this.mockChannel.Verify(mc => mc.Send(It.IsAny()), Times.Once); } [TestMethod] - public void InitializeExecutionShouldSendCommunicationMessageWithCorrectParameters() + public void InitializeDiscoveryShouldSendCommunicationMessageWithCorrectParametersWithVersion() { - var paths = new List() { "Hello", "World" }; - this.testRequestSender.InitializeExecution(paths); + this.SetupFakeChannelWithVersionNegotiation(DUMMYNEGOTIATEDPROTOCOLVERSION); + + this.testRequestSender.InitializeDiscovery(this.pathToAdditionalExtensions); - this.mockCommunicationManager.Verify(mc => mc.SendMessage(MessageType.ExecutionInitialize, paths, this.protocolConfig.Version), Times.Once); + this.mockDataSerializer.Verify(d => d.SerializePayload(MessageType.DiscoveryInitialize, this.pathToAdditionalExtensions, DUMMYNEGOTIATEDPROTOCOLVERSION), Times.Once); } [TestMethod] - public void DiscoverTestsShouldCallHandleDiscoveredTestsOnTestCaseEvent() + public void DiscoverTestsShouldSendStartDiscoveryMessageOnChannel() { - var sources = new List() { "Hello", "World" }; - string settingsXml = "SettingsXml"; - var mockHandler = new Mock(); - var discoveryCriteria = new DiscoveryCriteria(sources, 100, settingsXml); + this.SetupFakeCommunicationChannel(); - var testCases = new List() { new TestCase("x.y.z", new Uri("x://y"), "x.dll") }; - var rawMessage = "OnDiscoveredTests"; - var message = new Message() { MessageType = MessageType.TestCasesFound, Payload = null }; + this.testRequestSender.DiscoverTests(new DiscoveryCriteria(), this.mockDiscoveryEventsHandler.Object); - this.SetupReceiveRawMessageAsyncAndDeserializeMessageAndInitialize(rawMessage, message); - this.mockDataSerializer.Setup(ds => ds.DeserializePayload>(message)).Returns(testCases); + this.mockDataSerializer.Verify( + s => s.SerializePayload(MessageType.StartDiscovery, It.IsAny(), DEFAULTPROTOCOLVERSION), + Times.Once); + this.mockChannel.Verify(mc => mc.Send(It.IsAny()), Times.Once); + } - var completePayload = new DiscoveryCompletePayload() - { - IsAborted = false, - LastDiscoveredTests = null, - TotalTests = 1 - }; - var completeMessage = new Message() { MessageType = MessageType.DiscoveryComplete, Payload = null }; - mockHandler.Setup(mh => mh.HandleDiscoveredTests(testCases)).Callback( - () => - { - this.mockDataSerializer.Setup(ds => ds.DeserializeMessage(It.IsAny())).Returns(completeMessage); - this.mockDataSerializer.Setup(ds => ds.DeserializePayload(completeMessage)).Returns(completePayload); - }); + [TestMethod] + public void DiscoverTestsShouldSendStartDiscoveryMessageOnChannelWithVersion() + { + this.SetupFakeChannelWithVersionNegotiation(DUMMYNEGOTIATEDPROTOCOLVERSION); - this.testRequestSender.DiscoverTests(discoveryCriteria, mockHandler.Object); + this.testRequestSender.DiscoverTests(new DiscoveryCriteria(), this.mockDiscoveryEventsHandler.Object); - this.mockCommunicationManager.Verify(mc => mc.SendMessage(MessageType.StartDiscovery, discoveryCriteria, this.protocolConfig.Version), Times.Once); - this.mockDataSerializer.Verify(ds => ds.DeserializeMessage(rawMessage), Times.Exactly(2)); - mockHandler.Verify(mh => mh.HandleDiscoveredTests(testCases), Times.Once); - mockHandler.Verify(mh => mh.HandleRawMessage(rawMessage), Times.Exactly(2)); + this.mockDataSerializer.Verify( + s => s.SerializePayload(MessageType.StartDiscovery, It.IsAny(), DUMMYNEGOTIATEDPROTOCOLVERSION), + Times.Once); } [TestMethod] - public void DiscoverTestsShouldCallHandleLogMessageOnTestMessage() + public void DiscoverTestsShouldNotifyRawMessageOnMessageReceived() { - var sources = new List() { "Hello", "World" }; - string settingsXml = "SettingsXml"; - var mockHandler = new Mock(); - var discoveryCriteria = new DiscoveryCriteria(sources, 100, settingsXml); + this.SetupDeserializeMessage(MessageType.TestMessage, new TestMessagePayload()); + this.SetupFakeCommunicationChannel(); - var rawMessage = "TestMessage"; - var messagePayload = new TestMessagePayload() { MessageLevel = TestMessageLevel.Error, Message = rawMessage }; - var message = new Message() { MessageType = MessageType.TestMessage, Payload = null }; + this.testRequestSender.DiscoverTests(new DiscoveryCriteria(), this.mockDiscoveryEventsHandler.Object); - this.SetupReceiveRawMessageAsyncAndDeserializeMessageAndInitialize(rawMessage, message); - this.mockDataSerializer.Setup(ds => ds.DeserializePayload(message)).Returns(messagePayload); + this.RaiseMessageReceivedEvent(); + this.mockDiscoveryEventsHandler.Verify(eh => eh.HandleRawMessage("DummyData"), Times.Once); + } - var completePayload = new DiscoveryCompletePayload() - { - IsAborted = false, - LastDiscoveredTests = null, - TotalTests = 1 - }; - var completeMessage = new Message() { MessageType = MessageType.DiscoveryComplete, Payload = null }; - mockHandler.Setup(mh => mh.HandleLogMessage(TestMessageLevel.Error, rawMessage)).Callback( - () => - { - this.mockDataSerializer.Setup(ds => ds.DeserializeMessage(It.IsAny())).Returns(completeMessage); - this.mockDataSerializer.Setup(ds => ds.DeserializePayload(completeMessage)).Returns(completePayload); - }); + [TestMethod] + public void DiscoverTestsShouldNotifyDiscoveredTestsOnTestCasesFoundMessageReceived() + { + this.SetupDeserializeMessage>(MessageType.TestCasesFound, new TestCase[2]); + this.SetupFakeCommunicationChannel(); - this.testRequestSender.DiscoverTests(discoveryCriteria, mockHandler.Object); + this.testRequestSender.DiscoverTests(new DiscoveryCriteria(), this.mockDiscoveryEventsHandler.Object); - this.mockCommunicationManager.Verify(mc => mc.SendMessage(MessageType.StartDiscovery, discoveryCriteria, this.protocolConfig.Version), Times.Once); - this.mockDataSerializer.Verify(ds => ds.DeserializeMessage(rawMessage), Times.Exactly(2)); - mockHandler.Verify(mh => mh.HandleLogMessage(TestMessageLevel.Error, rawMessage), Times.Once); - mockHandler.Verify(mh => mh.HandleRawMessage(rawMessage), Times.Exactly(2)); + this.RaiseMessageReceivedEvent(); + this.mockDiscoveryEventsHandler.Verify(eh => eh.HandleDiscoveredTests(It.Is>(t => t.Count() == 2))); } [TestMethod] - public void DiscoverTestsShouldCallHandleDiscoveryCompleteOnDiscoveryCompletion() + public void DiscoverTestsShouldNotifyDiscoveryCompleteOnCompleteMessageReceived() { - var sources = new List() { "Hello", "World" }; - string settingsXml = "SettingsXml"; - var mockHandler = new Mock(); - var discoveryCriteria = new DiscoveryCriteria(sources, 100, settingsXml); + var completePayload = new DiscoveryCompletePayload { TotalTests = 10, IsAborted = false }; + this.SetupDeserializeMessage(MessageType.DiscoveryComplete, completePayload); + this.SetupFakeCommunicationChannel(); - var rawMessage = "RunComplete"; - var completePayload = new DiscoveryCompletePayload() - { - IsAborted = false, - LastDiscoveredTests = null, - TotalTests = 1 - }; - var message = new Message() { MessageType = MessageType.DiscoveryComplete, Payload = null }; + this.testRequestSender.DiscoverTests(new DiscoveryCriteria(), this.mockDiscoveryEventsHandler.Object); + + this.RaiseMessageReceivedEvent(); + this.mockDiscoveryEventsHandler.Verify(eh => eh.HandleDiscoveryComplete(It.Is(dc => dc.IsAborted == false && dc.TotalCount == 10), null)); + } - this.SetupReceiveRawMessageAsyncAndDeserializeMessageAndInitialize(rawMessage, message); - this.mockDataSerializer.Setup(ds => ds.DeserializePayload(message)).Returns(completePayload); + [TestMethod] + public void DiscoverTestShouldNotifyLogMessageOnTestMessageReceived() + { + var message = new TestMessagePayload { MessageLevel = TestMessageLevel.Error, Message = "Message1" }; + this.SetupDeserializeMessage(MessageType.TestMessage, message); + this.SetupFakeCommunicationChannel(); - this.testRequestSender.DiscoverTests(discoveryCriteria, mockHandler.Object); + this.testRequestSender.DiscoverTests(new DiscoveryCriteria(), this.mockDiscoveryEventsHandler.Object); - this.mockCommunicationManager.Verify(mc => mc.SendMessage(MessageType.StartDiscovery, discoveryCriteria, this.protocolConfig.Version), Times.Once); - this.mockDataSerializer.Verify(ds => ds.DeserializeMessage(rawMessage), Times.Once); - mockHandler.Verify(mh => mh.HandleDiscoveryComplete(It.IsAny(), null), Times.Once); - mockHandler.Verify(mh => mh.HandleRawMessage(rawMessage), Times.Once); + this.RaiseMessageReceivedEvent(); + this.mockDiscoveryEventsHandler.Verify(eh => eh.HandleLogMessage(TestMessageLevel.Error, "Message1")); } [TestMethod] - public void DiscoverTestsShouldCollectMetricsOnHandleDiscoveryComplete() + public void DiscoverTestShouldNotifyLogMessageIfExceptionThrownOnMessageReceived() { - var dict = new Dictionary(); - dict.Add("DummyMessage", "DummyValue"); + this.SetupExceptionOnMessageReceived(); + this.SetupFakeCommunicationChannel(); - var mockHandler = new Mock(); - var rawMessage = "RunComplete"; - var completePayload = new DiscoveryCompletePayload() - { - IsAborted = false, - LastDiscoveredTests = null, - TotalTests = 1, - Metrics = dict - }; - var message = new Message() { MessageType = MessageType.DiscoveryComplete, Payload = null }; + this.testRequestSender.DiscoverTests(new DiscoveryCriteria(), this.mockDiscoveryEventsHandler.Object); - this.SetupReceiveRawMessageAsyncAndDeserializeMessageAndInitialize(rawMessage, message); - this.mockDataSerializer.Setup(ds => ds.DeserializePayload(message)).Returns(completePayload); + this.RaiseMessageReceivedEvent(); + this.mockDiscoveryEventsHandler.Verify(eh => eh.HandleLogMessage(TestMessageLevel.Error, It.Is(s => s.Contains("Dummy Message")))); + this.mockDiscoveryEventsHandler.Verify(eh => eh.HandleRawMessage("SerializedMessage"), Times.Once); + } - DiscoveryCompleteEventArgs actualDiscoveryCompleteEventArgs = null; - mockHandler.Setup(md => md.HandleDiscoveryComplete(It.IsAny(), null)) - .Callback>( - (discoveryCompleteEventArgs, testCase) => - { - actualDiscoveryCompleteEventArgs = discoveryCompleteEventArgs; - }); + [TestMethod] + public void DiscoverTestShouldNotifyDiscoveryCompleteIfExceptionThrownOnMessageReceived() + { + this.SetupExceptionOnMessageReceived(); + this.SetupFakeCommunicationChannel(); - // Act. - this.testRequestSender.DiscoverTests(new DiscoveryCriteria(), mockHandler.Object); + this.testRequestSender.DiscoverTests(new DiscoveryCriteria(), this.mockDiscoveryEventsHandler.Object); - // Verify - Assert.AreEqual(actualDiscoveryCompleteEventArgs.Metrics, dict); + this.RaiseMessageReceivedEvent(); + this.mockDiscoveryEventsHandler.Verify(eh => eh.HandleDiscoveryComplete(It.Is(dc => dc.IsAborted == true && dc.TotalCount == -1), null)); } [TestMethod] - public void DiscoverTestsShouldHandleExceptionOnSendMessage() + public void DiscoverTestsShouldNotAbortDiscoveryIfClientDisconnectedAndOperationIsComplete() { - var sources = new List() { "Hello", "World" }; - string settingsXml = "SettingsXml"; - var mockHandler = new Mock(); - var discoveryCriteria = new DiscoveryCriteria(sources, 100, settingsXml); - var exception = new Exception(); - this.mockCommunicationManager.Setup(cm => cm.SendMessage(MessageType.StartDiscovery, discoveryCriteria, this.protocolConfig.Version)) - .Throws(exception); + var completePayload = new DiscoveryCompletePayload { TotalTests = 10, IsAborted = false }; + this.SetupDeserializeMessage(MessageType.DiscoveryComplete, completePayload); + this.SetupFakeCommunicationChannel(); + this.testRequestSender.DiscoverTests(new DiscoveryCriteria(), this.mockDiscoveryEventsHandler.Object); + this.RaiseMessageReceivedEvent(); // Raise discovery complete - this.testRequestSender.DiscoverTests(discoveryCriteria, mockHandler.Object); + this.RaiseClientDisconnectedEvent(); - mockHandler.Verify(mh => mh.HandleDiscoveryComplete(It.IsAny(), null), Times.Once); - mockHandler.Verify(mh => mh.HandleLogMessage(TestMessageLevel.Error, It.IsAny()), Times.Once); - mockHandler.Verify(mh => mh.HandleRawMessage(It.IsAny()), Times.Exactly(2)); + this.mockDiscoveryEventsHandler.Verify(eh => eh.HandleLogMessage(TestMessageLevel.Error, It.IsAny()), Times.Never); + this.mockDiscoveryEventsHandler.Verify(eh => eh.HandleDiscoveryComplete(new DiscoveryCompleteEventArgs(-1, true), null), Times.Never); } [TestMethod] - public void DiscoverTestsShouldHandleDiscoveryCompleteOnCommunicationFailure() + public void DiscoverTestShouldNotifyLogMessageIfClientDisconnected() { - this.DiscoverTestsErrorScenarioTestTemplates(CommunicationUtilitiesResources.UnableToCommunicateToTestHost, (s) => { }); + // Expect default error message since we've not set any client exit message + var expectedErrorMessage = "Reason: Unable to communicate"; + this.SetupFakeCommunicationChannel(); + this.mockDataSerializer.Setup(ds => ds.SerializePayload(MessageType.TestMessage, It.Is(p => p.Message.Contains(expectedErrorMessage)))) + .Returns("Serialized error"); + this.testRequestSender.DiscoverTests(new DiscoveryCriteria(), this.mockDiscoveryEventsHandler.Object); + + this.RaiseClientDisconnectedEvent(); + + this.mockDiscoveryEventsHandler.Verify(eh => eh.HandleLogMessage(TestMessageLevel.Error, It.Is(s => s.Contains(expectedErrorMessage)))); + this.mockDiscoveryEventsHandler.Verify(eh => eh.HandleRawMessage(It.Is(s => !string.IsNullOrEmpty(s) && s.Equals("Serialized error"))), Times.Once); } [TestMethod] - public void DiscoverTestsShouldHandleDiscoveryCompleteOnProcessExit() + public void DiscoverTestShouldNotifyLogMessageIfClientDisconnectedWithClientExit() { - this.DiscoverTestsErrorScenarioTestTemplates("Error Message", (s) => this.testRequestSender.OnClientProcessExit(s)); + this.SetupFakeCommunicationChannel(); + this.mockDataSerializer.Setup(ds => ds.SerializePayload(MessageType.TestMessage, It.Is(p => p.Message.Contains("Dummy Stderr")))) + .Returns("Serialized Stderr"); + this.testRequestSender.DiscoverTests(new DiscoveryCriteria(), this.mockDiscoveryEventsHandler.Object); + this.testRequestSender.OnClientProcessExit("Dummy Stderr"); + + this.RaiseClientDisconnectedEvent(); + + this.mockDiscoveryEventsHandler.Verify(eh => eh.HandleLogMessage(TestMessageLevel.Error, It.Is(s => s.Contains("Dummy Stderr"))), Times.Once); + this.mockDiscoveryEventsHandler.Verify(eh => eh.HandleRawMessage(It.Is(s => !string.IsNullOrEmpty(s) && s.Equals("Serialized Stderr"))), Times.Once); } - public void DiscoverTestsErrorScenarioTestTemplates(string errorMessage, Action exitCallback) + [TestMethod] + public void DiscoverTestShouldNotifyDiscoveryCompleteIfClientDisconnected() { - var sources = new List() { "Hello", "World" }; - string settingsXml = "SettingsXml"; - var mockHandler = new Mock(); - var discoveryCriteria = new DiscoveryCriteria(sources, 100, settingsXml); - this.mockCommunicationManager.Setup(cm => cm.ReceiveRawMessageAsync(It.IsAny())) - .Returns(Task.FromResult((string)null)) - .Callback(() => exitCallback(errorMessage)); + this.SetupFakeCommunicationChannel(); + this.testRequestSender.DiscoverTests(new DiscoveryCriteria(), this.mockDiscoveryEventsHandler.Object); + + this.RaiseClientDisconnectedEvent(); + + this.mockDiscoveryEventsHandler.Verify(eh => eh.HandleDiscoveryComplete(It.Is(dc => dc.IsAborted == true && dc.TotalCount == -1), null)); + } - this.mockCommunicationManager.Setup(mc => mc.HostServer(It.IsAny())) - .Returns(new IPEndPoint(IPAddress.Loopback, 0)); + #endregion - this.testRequestSender.InitializeCommunication(); - this.testRequestSender.DiscoverTests(discoveryCriteria, mockHandler.Object); + #region Execution Protocol Tests - mockHandler.Verify(mh => mh.HandleDiscoveryComplete(It.IsAny(), null), Times.Once); - mockHandler.Verify(mh => mh.HandleLogMessage(TestMessageLevel.Error, string.Format(CommunicationUtilitiesResources.AbortedTestDiscovery, errorMessage)), Times.Once); - mockHandler.Verify(mh => mh.HandleRawMessage(It.IsAny()), Times.Exactly(2)); + [TestMethod] + public void InitializeExecutionShouldSendCommunicationMessageWithCorrectParameters() + { + this.SetupFakeCommunicationChannel(); + + this.testRequestSender.InitializeExecution(this.pathToAdditionalExtensions); + + this.mockDataSerializer.Verify(d => d.SerializePayload(MessageType.ExecutionInitialize, this.pathToAdditionalExtensions, 1), Times.Once); + this.mockChannel.Verify(mc => mc.Send(It.IsAny()), Times.Once); } [TestMethod] - public void StartTestRunWithSourcesShouldCallHandleTestRunStatsChange() + public void InitializeExecutionShouldSendCommunicationMessageWithCorrectParametersWithVersion() { - var mockHandler = new Mock(); - var runCriteria = new TestRunCriteriaWithSources(null, null, null, null); + this.SetupFakeChannelWithVersionNegotiation(DUMMYNEGOTIATEDPROTOCOLVERSION); - var testRunChangedArgs = new TestRunChangedEventArgs(null, null, null); - var rawMessage = "OnTestRunStatsChange"; - var message = new Message() { MessageType = MessageType.TestRunStatsChange, Payload = null }; + this.testRequestSender.InitializeExecution(this.pathToAdditionalExtensions); - this.SetupReceiveRawMessageAsyncAndDeserializeMessageAndInitialize(rawMessage, message); - this.mockDataSerializer.Setup(ds => ds.DeserializePayload(message)).Returns(testRunChangedArgs); + this.mockDataSerializer.Verify(d => d.SerializePayload(MessageType.ExecutionInitialize, this.pathToAdditionalExtensions, DUMMYNEGOTIATEDPROTOCOLVERSION), Times.Once); + } - var completePayload = new TestRunCompletePayload() - { - ExecutorUris = null, - LastRunTests = null, - RunAttachments = null, - TestRunCompleteArgs = null - }; - var completeMessage = new Message() { MessageType = MessageType.ExecutionComplete, Payload = null }; - mockHandler.Setup(mh => mh.HandleTestRunStatsChange(testRunChangedArgs)).Callback( - () => - { - this.mockDataSerializer.Setup(ds => ds.DeserializeMessage(It.IsAny())).Returns(completeMessage); - this.mockDataSerializer.Setup(ds => ds.DeserializePayload(completeMessage)).Returns(completePayload); - }); + [TestMethod] + public void StartTestRunShouldSendStartTestExecutionWithSourcesOnChannel() + { + this.SetupFakeCommunicationChannel(); - var waitHandle = new AutoResetEvent(false); - mockHandler.Setup(mh => mh.HandleTestRunComplete( - It.IsAny(), - It.IsAny(), - It.IsAny>(), - It.IsAny>())).Callback(() => waitHandle.Set()); + this.testRequestSender.StartTestRun(this.testRunCriteriaWithSources, this.mockExecutionEventsHandler.Object); - this.testRequestSender.StartTestRun(runCriteria, mockHandler.Object); + this.mockDataSerializer.Verify(d => d.SerializePayload(MessageType.StartTestExecutionWithSources, this.testRunCriteriaWithSources, DEFAULTPROTOCOLVERSION), Times.Once); + this.mockChannel.Verify(mc => mc.Send(It.IsAny()), Times.Once); + } - waitHandle.WaitOne(); + [TestMethod] + public void StartTestRunShouldSendStartTestExecutionWithSourcesOnChannelWithVersion() + { + this.SetupFakeChannelWithVersionNegotiation(DUMMYNEGOTIATEDPROTOCOLVERSION); - this.mockCommunicationManager.Verify(mc => mc.SendMessage(MessageType.StartTestExecutionWithSources, runCriteria, this.protocolConfig.Version), Times.Once); + this.testRequestSender.StartTestRun(this.testRunCriteriaWithSources, this.mockExecutionEventsHandler.Object); - // One for run stats and another for runcomplete - this.mockDataSerializer.Verify(ds => ds.DeserializeMessage(rawMessage), Times.Exactly(2)); - mockHandler.Verify(mh => mh.HandleTestRunStatsChange(testRunChangedArgs), Times.Once); - mockHandler.Verify(mh => mh.HandleRawMessage(rawMessage), Times.Exactly(2)); + this.mockDataSerializer.Verify(d => d.SerializePayload(MessageType.StartTestExecutionWithSources, this.testRunCriteriaWithSources, DUMMYNEGOTIATEDPROTOCOLVERSION), Times.Once); } [TestMethod] - public void StartTestRunWithTestsShouldCallHandleTestRunStatsChange() + public void StartTestRunWithTestsShouldSendStartTestExecutionWithTestsOnChannel() { - var mockHandler = new Mock(); - var runCriteria = new TestRunCriteriaWithTests(null, null, null, null); + var runCriteria = new TestRunCriteriaWithTests(new TestCase[2], "runsettings", null, null); + this.SetupFakeCommunicationChannel(); - var testRunChangedArgs = new TestRunChangedEventArgs(null, null, null); - var rawMessage = "OnTestRunStatsChange"; - var message = new Message() { MessageType = MessageType.TestRunStatsChange, Payload = null }; + this.testRequestSender.StartTestRun(runCriteria, this.mockExecutionEventsHandler.Object); - this.SetupReceiveRawMessageAsyncAndDeserializeMessageAndInitialize(rawMessage, message); - this.mockDataSerializer.Setup(ds => ds.DeserializePayload(message)).Returns(testRunChangedArgs); + this.mockDataSerializer.Verify(d => d.SerializePayload(MessageType.StartTestExecutionWithTests, runCriteria, DEFAULTPROTOCOLVERSION), Times.Once); + this.mockChannel.Verify(mc => mc.Send(It.IsAny()), Times.Once); + } - var completePayload = new TestRunCompletePayload() - { - ExecutorUris = null, - LastRunTests = null, - RunAttachments = null, - TestRunCompleteArgs = null - }; - var completeMessage = new Message() { MessageType = MessageType.ExecutionComplete, Payload = null }; - var waitHandle = new AutoResetEvent(false); - mockHandler.Setup(mh => mh.HandleTestRunStatsChange(testRunChangedArgs)).Callback( - () => - { - this.mockDataSerializer.Setup(ds => ds.DeserializeMessage(It.IsAny())).Returns(completeMessage); - this.mockDataSerializer.Setup(ds => ds.DeserializePayload(completeMessage)).Returns(completePayload); - waitHandle.Set(); - }); + [TestMethod] + public void StartTestRunWithTestsShouldSendStartTestExecutionWithTestsOnChannelWithVersion() + { + var runCriteria = new TestRunCriteriaWithTests(new TestCase[2], "runsettings", null, null); + this.SetupFakeChannelWithVersionNegotiation(DUMMYNEGOTIATEDPROTOCOLVERSION); + + this.testRequestSender.StartTestRun(runCriteria, this.mockExecutionEventsHandler.Object); + + this.mockDataSerializer.Verify(d => d.SerializePayload(MessageType.StartTestExecutionWithTests, runCriteria, DUMMYNEGOTIATEDPROTOCOLVERSION), Times.Once); + } - this.testRequestSender.StartTestRun(runCriteria, mockHandler.Object); + [TestMethod] + public void StartTestRunShouldNotifyRawMessageOnMessageReceived() + { + this.SetupDeserializeMessage(MessageType.TestMessage, new TestMessagePayload()); + this.SetupFakeCommunicationChannel(); + + this.testRequestSender.StartTestRun(this.testRunCriteriaWithSources, this.mockExecutionEventsHandler.Object); - waitHandle.WaitOne(); - this.mockCommunicationManager.Verify(mc => mc.SendMessage(MessageType.StartTestExecutionWithTests, runCriteria, this.protocolConfig.Version), Times.Once); - mockHandler.Verify(mh => mh.HandleTestRunStatsChange(testRunChangedArgs), Times.Once); - mockHandler.Verify(mh => mh.HandleRawMessage(rawMessage), Times.AtLeastOnce); + this.RaiseMessageReceivedEvent(); + this.mockExecutionEventsHandler.Verify(eh => eh.HandleRawMessage("DummyData"), Times.Once); } [TestMethod] - public void StartTestRunShouldCallHandleLogMessageOnTestMessage() + public void StartTestRunShouldNotifyTestRunStatsChangeOnRunStatsMessageReceived() { - var mockHandler = new Mock(); - var runCriteria = new TestRunCriteriaWithSources(null, null, null, null); + var testRunChangedArgs = new TestRunChangedEventArgs( + null, + new Microsoft.VisualStudio.TestPlatform.ObjectModel.TestResult[2], + new TestCase[2]); + this.SetupDeserializeMessage(MessageType.TestRunStatsChange, testRunChangedArgs); + this.SetupFakeCommunicationChannel(); - var rawMessage = "OnLogMessage"; - var message = new Message() { MessageType = MessageType.TestMessage, Payload = null }; - var payload = new TestMessagePayload() { MessageLevel = TestMessageLevel.Error, Message = rawMessage }; + this.testRequestSender.StartTestRun(this.testRunCriteriaWithSources, this.mockExecutionEventsHandler.Object); - this.SetupReceiveRawMessageAsyncAndDeserializeMessageAndInitialize(rawMessage, message); - this.mockDataSerializer.Setup(ds => ds.DeserializePayload(message)).Returns(payload); + this.RaiseMessageReceivedEvent(); + this.mockExecutionEventsHandler.Verify(eh => eh.HandleTestRunStatsChange(testRunChangedArgs), Times.Once); + } - var completePayload = new TestRunCompletePayload() + [TestMethod] + public void StartTestRunShouldNotifyExecutionCompleteOnRunCompleteMessageReceived() + { + var testRunCompletePayload = new TestRunCompletePayload { - ExecutorUris = null, LastRunTests = null, RunAttachments = null, TestRunCompleteArgs = null + TestRunCompleteArgs = new TestRunCompleteEventArgs(null, false, false, null, null, TimeSpan.MaxValue), + LastRunTests = new TestRunChangedEventArgs(null, null, null), + RunAttachments = new List() }; - var completeMessage = new Message() { MessageType = MessageType.ExecutionComplete, Payload = null }; - var waitHandle = new AutoResetEvent(false); - mockHandler.Setup(mh => mh.HandleLogMessage(TestMessageLevel.Error, rawMessage)).Callback( - () => - { - this.mockDataSerializer.Setup(ds => ds.DeserializeMessage(It.IsAny())).Returns(completeMessage); - this.mockDataSerializer.Setup(ds => ds.DeserializePayload(completeMessage)).Callback(() => { waitHandle.Set(); }) - .Returns(completePayload); - }); + this.SetupDeserializeMessage(MessageType.ExecutionComplete, testRunCompletePayload); + this.SetupFakeCommunicationChannel(); - this.testRequestSender.StartTestRun(runCriteria, mockHandler.Object); - waitHandle.WaitOne(); + this.testRequestSender.StartTestRun(this.testRunCriteriaWithSources, this.mockExecutionEventsHandler.Object); + + this.RaiseMessageReceivedEvent(); + this.mockExecutionEventsHandler.Verify( + eh => eh.HandleTestRunComplete( + testRunCompletePayload.TestRunCompleteArgs, + testRunCompletePayload.LastRunTests, + testRunCompletePayload.RunAttachments, + It.IsAny>()), + Times.Once); + } + + [TestMethod] + public void StartTestRunShouldNotifyLogMessageOnTestMessageReceived() + { + var testMessagePayload = new TestMessagePayload { MessageLevel = TestMessageLevel.Error, Message = "Dummy" }; + this.SetupDeserializeMessage(MessageType.TestMessage, testMessagePayload); + this.SetupFakeCommunicationChannel(); - this.mockCommunicationManager.Verify(mc => mc.SendMessage(MessageType.StartTestExecutionWithSources, runCriteria, this.protocolConfig.Version), Times.Once); - this.mockDataSerializer.Verify(ds => ds.DeserializeMessage(It.IsAny()), Times.AtLeast(2)); + this.testRequestSender.StartTestRun(this.testRunCriteriaWithSources, this.mockExecutionEventsHandler.Object); - // Asserting that 'StartTestRun" should have been completed, & invoked only once - this.mockDataSerializer.Verify(ds => ds.DeserializePayload(completeMessage), Times.Exactly(1)); - mockHandler.Verify(mh => mh.HandleLogMessage(payload.MessageLevel, payload.Message), Times.Once); - mockHandler.Verify(mh => mh.HandleRawMessage(rawMessage), Times.AtLeastOnce); + this.RaiseMessageReceivedEvent(); + this.mockExecutionEventsHandler.Verify(eh => eh.HandleLogMessage(TestMessageLevel.Error, "Dummy"), Times.Once); } [TestMethod] - public void StartTestRunShouldCallLaunchProcessWithDebuggerAndWaitForCallback() + public void StartTestRunShouldNotifyLaunchWithDebuggerOnMessageReceived() { - var mockHandler = new Mock(); - var runCriteria = new TestRunCriteriaWithSources(null, null, null, null); + var launchMessagePayload = new TestProcessStartInfo(); + this.SetupDeserializeMessage(MessageType.LaunchAdapterProcessWithDebuggerAttached, launchMessagePayload); + this.SetupFakeCommunicationChannel(); - var rawMessage = "LaunchProcessWithDebugger"; - var message = new Message() { MessageType = MessageType.LaunchAdapterProcessWithDebuggerAttached, Payload = null }; - var payload = new TestProcessStartInfo(); + this.testRequestSender.StartTestRun(this.testRunCriteriaWithSources, this.mockExecutionEventsHandler.Object); - this.SetupReceiveRawMessageAsyncAndDeserializeMessageAndInitialize(rawMessage, message); - this.mockDataSerializer.Setup(ds => ds.DeserializePayload(message)).Returns(payload); + this.RaiseMessageReceivedEvent(); + this.mockExecutionEventsHandler.Verify(eh => eh.LaunchProcessWithDebuggerAttached(launchMessagePayload), Times.Once); + } - var completePayload = new TestRunCompletePayload() - { - ExecutorUris = null, - LastRunTests = null, - RunAttachments = null, - TestRunCompleteArgs = null - }; - var completeMessage = new Message() { MessageType = MessageType.ExecutionComplete, Payload = null }; - mockHandler.Setup(mh => mh.LaunchProcessWithDebuggerAttached(payload)).Callback( - () => - { - this.mockDataSerializer.Setup(ds => ds.DeserializeMessage(It.IsAny())).Returns(completeMessage); - this.mockDataSerializer.Setup(ds => ds.DeserializePayload(completeMessage)).Returns(completePayload); - }); + [TestMethod] + public void StartTestRunShouldSendLaunchDebuggerAttachedCallbackOnMessageReceived() + { + var launchMessagePayload = new TestProcessStartInfo(); + this.SetupDeserializeMessage(MessageType.LaunchAdapterProcessWithDebuggerAttached, launchMessagePayload); + this.SetupFakeCommunicationChannel(); - var waitHandle = new AutoResetEvent(false); - mockHandler.Setup(mh => mh.HandleTestRunComplete( - It.IsAny(), - It.IsAny(), - It.IsAny>(), - It.IsAny>())).Callback(() => waitHandle.Set()); + this.testRequestSender.StartTestRun(this.testRunCriteriaWithSources, this.mockExecutionEventsHandler.Object); - this.testRequestSender.StartTestRun(runCriteria, mockHandler.Object); + this.RaiseMessageReceivedEvent(); + this.mockDataSerializer.Verify(ds => ds.SerializePayload(MessageType.LaunchAdapterProcessWithDebuggerAttachedCallback, It.IsAny(), 1), Times.Once); + this.mockChannel.Verify(c => c.Send(It.IsAny()), Times.AtLeastOnce); + } - waitHandle.WaitOne(); + [TestMethod] + public void StartTestRunShouldSendLaunchDebuggerAttachedCallbackOnMessageReceivedWithVersion() + { + var launchMessagePayload = new TestProcessStartInfo(); + this.SetupFakeChannelWithVersionNegotiation(DUMMYNEGOTIATEDPROTOCOLVERSION); + this.SetupDeserializeMessage(MessageType.LaunchAdapterProcessWithDebuggerAttached, launchMessagePayload); - this.mockCommunicationManager.Verify(mc => mc.SendMessage(MessageType.StartTestExecutionWithSources, runCriteria, this.protocolConfig.Version), Times.Once); + this.testRequestSender.StartTestRun(this.testRunCriteriaWithSources, this.mockExecutionEventsHandler.Object); - this.mockDataSerializer.Verify(ds => ds.DeserializeMessage(It.IsAny()), Times.Exactly(2)); - mockHandler.Verify(mh => mh.LaunchProcessWithDebuggerAttached(payload), Times.Once); - mockHandler.Verify(mh => mh.HandleRawMessage(rawMessage), Times.Exactly(2)); - this.mockCommunicationManager.Verify(mc => mc.SendMessage(MessageType.LaunchAdapterProcessWithDebuggerAttachedCallback, It.IsAny(), this.protocolConfig.Version), Times.Once); + this.RaiseMessageReceivedEvent(); + this.mockDataSerializer.Verify(ds => ds.SerializePayload(MessageType.LaunchAdapterProcessWithDebuggerAttachedCallback, It.IsAny(), DUMMYNEGOTIATEDPROTOCOLVERSION), Times.Once); } [TestMethod] - public void StartTestRunShouldCallHandleTestRunCompleteOnRunCompletion() + public void StartTestRunShouldNotifyLogMessageIfExceptionIsThrownOnMessageReceived() { - var mockHandler = new Mock(); - var runCriteria = new TestRunCriteriaWithTests(null, null, null, null); + this.SetupExceptionOnMessageReceived(); + this.SetupFakeCommunicationChannel(); - var rawMessage = "ExecComplete"; - var message = new Message() { MessageType = MessageType.ExecutionComplete, Payload = null }; - var payload = new TestRunCompletePayload() { ExecutorUris = null, LastRunTests = null, RunAttachments = null, TestRunCompleteArgs = null }; + this.testRequestSender.StartTestRun(this.testRunCriteriaWithSources, this.mockExecutionEventsHandler.Object); - this.SetupReceiveRawMessageAsyncAndDeserializeMessageAndInitialize(rawMessage, message); - this.mockDataSerializer.Setup(ds => ds.DeserializePayload(message)).Returns(payload); + this.RaiseMessageReceivedEvent(); + this.mockExecutionEventsHandler.Verify(eh => eh.HandleLogMessage(TestMessageLevel.Error, It.Is(s => s.Contains("Dummy Message"))), Times.Once); + this.mockExecutionEventsHandler.Verify(eh => eh.HandleRawMessage("SerializedMessage"), Times.Once); + } - var waitHandle = new AutoResetEvent(false); - mockHandler.Setup(mh => mh.HandleTestRunComplete( - It.IsAny(), - It.IsAny(), - It.IsAny>(), - It.IsAny>())).Callback(() => waitHandle.Set()); + [TestMethod] + public void StartTestRunShouldNotifyExecutionCompleteIfExceptionIsThrownOnMessageReceived() + { + this.SetupExceptionOnMessageReceived(); + this.SetupFakeCommunicationChannel(); - this.testRequestSender.StartTestRun(runCriteria, mockHandler.Object); + this.testRequestSender.StartTestRun(this.testRunCriteriaWithSources, this.mockExecutionEventsHandler.Object); - waitHandle.WaitOne(); + this.RaiseMessageReceivedEvent(); + this.mockExecutionEventsHandler.Verify(eh => eh.HandleTestRunComplete(It.Is(t => t.IsAborted), null, null, null), Times.Once); + this.mockExecutionEventsHandler.Verify(eh => eh.HandleRawMessage("SerializedAbortedPayload"), Times.Once); + } + + [TestMethod] + public void StartTestRunShouldNotNotifyExecutionCompleteIfClientDisconnectedAndOperationComplete() + { + var testRunCompletePayload = new TestRunCompletePayload + { + TestRunCompleteArgs = new TestRunCompleteEventArgs(null, false, false, null, null, TimeSpan.MaxValue), + LastRunTests = new TestRunChangedEventArgs(null, null, null), + RunAttachments = new List() + }; + this.SetupDeserializeMessage(MessageType.ExecutionComplete, testRunCompletePayload); + this.SetupFakeCommunicationChannel(); + this.testRequestSender.StartTestRun(this.testRunCriteriaWithSources, this.mockExecutionEventsHandler.Object); + this.RaiseMessageReceivedEvent(); // Raise test run complete + + this.RaiseClientDisconnectedEvent(); - this.mockCommunicationManager.Verify(mc => mc.SendMessage(MessageType.StartTestExecutionWithTests, runCriteria, this.protocolConfig.Version), Times.Once); - this.mockDataSerializer.Verify(ds => ds.DeserializeMessage(rawMessage), Times.Once); - mockHandler.Verify(mh => mh.HandleTestRunComplete(null, null, null, null), Times.Once); - mockHandler.Verify(mh => mh.HandleRawMessage(rawMessage), Times.Once); + this.mockExecutionEventsHandler.Verify(eh => eh.HandleTestRunComplete(It.Is(t => t.IsAborted), null, null, null), Times.Never); + this.mockExecutionEventsHandler.Verify(eh => eh.HandleRawMessage("SerializedAbortedPayload"), Times.Never); } [TestMethod] - public void StartTestRunShouldCallHandleTestRunCompleteAndHandleLogMessageOnConnectionBreak() + public void StartTestRunShouldNotifyErrorLogMessageIfClientDisconnected() { - this.StartTestRunErrorTestsTemplate(CommunicationUtilitiesResources.UnableToCommunicateToTestHost, (s) => { }); + this.SetupFakeCommunicationChannel(); + this.testRequestSender.StartTestRun(this.testRunCriteriaWithSources, this.mockExecutionEventsHandler.Object); + + this.RaiseClientDisconnectedEvent(); + + // Expect default error message since we've not set any client exit message + var expectedErrorMessage = "Reason: Unable to communicate"; + this.mockExecutionEventsHandler.Verify(eh => eh.HandleLogMessage(TestMessageLevel.Error, It.Is(s => s.Contains(expectedErrorMessage))), Times.Once); } [TestMethod] - public void StartTestRunShouldCallHandleTestRunCompleteAndHandleLogMessageOnProcessExit() + public void StartTestRunShouldNotifyErrorLogMessageIfClientDisconnectedWithClientExit() { - this.StartTestRunErrorTestsTemplate("Error Message", (s) => this.testRequestSender.OnClientProcessExit(s)); + this.SetupFakeCommunicationChannel(); + this.testRequestSender.StartTestRun(this.testRunCriteriaWithSources, this.mockExecutionEventsHandler.Object); + this.testRequestSender.OnClientProcessExit("Dummy Stderr"); + + this.RaiseClientDisconnectedEvent(); + + var expectedErrorMessage = "Reason: Dummy Stderr"; + this.mockExecutionEventsHandler.Verify(eh => eh.HandleLogMessage(TestMessageLevel.Error, It.Is(s => s.Contains(expectedErrorMessage))), Times.Once); } [TestMethod] - public void EndSessionShouldSendCorrectEventMessage() + public void StartTestRunShouldNotifyExecutionCompleteIfClientDisconnected() { - this.testRequestSender.EndSession(); + this.SetupOperationAbortedPayload(); + this.SetupFakeCommunicationChannel(); + this.testRequestSender.StartTestRun(this.testRunCriteriaWithSources, this.mockExecutionEventsHandler.Object); + + this.RaiseClientDisconnectedEvent(); - this.mockCommunicationManager.Verify(mc => mc.SendMessage(MessageType.SessionEnd), Times.Once); + this.mockExecutionEventsHandler.Verify(eh => eh.HandleTestRunComplete(It.Is(t => t.IsAborted), null, null, null), Times.Once); + this.mockExecutionEventsHandler.Verify(eh => eh.HandleRawMessage("SerializedAbortedPayload"), Times.Once); } [TestMethod] - public void CancelTestRunSessionShouldSendCorrectEventMessage() + public void SendTestRunCancelShouldSendCancelTestRunMessage() { + this.SetupFakeCommunicationChannel(); + this.testRequestSender.SendTestRunCancel(); - this.mockCommunicationManager.Verify(mc => mc.SendMessage(MessageType.CancelTestRun), Times.Once); + this.mockDataSerializer.Verify(ds => ds.SerializeMessage(MessageType.CancelTestRun), Times.Once); + this.mockChannel.Verify(c => c.Send(It.IsAny()), Times.Once); } - private void CheckAndSetProtocolVersion() + [TestMethod] + public void SendTestRunAbortShouldSendAbortTestRunMessage() + { + this.SetupFakeCommunicationChannel(); + + this.testRequestSender.SendTestRunAbort(); + + this.mockDataSerializer.Verify(ds => ds.SerializeMessage(MessageType.AbortTestRun), Times.Once); + this.mockChannel.Verify(c => c.Send(It.IsAny()), Times.Once); + } + + #endregion + + private string SetupFakeCommunicationChannel(string connectionArgs = "123") { - var message = new Message() { MessageType = MessageType.VersionCheck, Payload = this.protocolConfig.Version }; - this.mockCommunicationManager.Setup(mc => mc.ReceiveMessage()).Returns(message); - this.mockDataSerializer.Setup(ds => ds.DeserializePayload(It.IsAny())).Returns(this.protocolConfig.Version); + this.connectionInfo = new TestHostConnectionInfo + { + Endpoint = IPAddress.Loopback + ":" + connectionArgs, + Role = ConnectionRole.Client, + Transport = Transport.Sockets + }; + + // Setup mock connected event and initialize communication channel + this.mockServer.Setup(mc => mc.Start(this.connectionInfo.Endpoint)) + .Returns(this.connectionInfo.Endpoint) + .Callback(() => this.mockServer.Raise(s => s.Connected += null, this.mockServer.Object, this.connectedEventArgs)); + + return this.testRequestSender.InitializeCommunication().ToString(); + } + + private void SetupFakeChannelWithVersionNegotiation(int protocolVersion) + { + // Sends a check version message to setup the negotiated protocol version. + // This method is only required in specific tests. + this.SetupDeserializeMessage(MessageType.VersionCheck, DUMMYNEGOTIATEDPROTOCOLVERSION); + this.SetupRaiseMessageReceivedOnCheckVersion(); + this.SetupFakeCommunicationChannel(); this.testRequestSender.CheckVersionWithTestHost(); + this.ResetRaiseMessageReceivedOnCheckVersion(); + } + + private void RaiseMessageReceivedEvent() + { + this.mockChannel.Raise( + c => c.MessageReceived += null, + this.mockChannel.Object, + new MessageReceivedEventArgs { Data = "DummyData" }); } - private void SetupReceiveRawMessageAsyncAndDeserializeMessageAndInitialize(string rawMessage, Message message) + private void RaiseClientDisconnectedEvent() { - this.mockCommunicationManager.Setup(mc => mc.HostServer(It.IsAny())).Returns(new IPEndPoint(IPAddress.Loopback, 0)); - this.testRequestSender.InitializeCommunication(); - this.mockCommunicationManager.Setup(mc => mc.ReceiveRawMessageAsync(It.IsAny())).Returns(Task.FromResult(rawMessage)); - this.mockDataSerializer.Setup(ds => ds.DeserializeMessage(rawMessage)).Returns(message); + this.mockServer.Raise( + s => s.Disconnected += null, + this.mockServer.Object, + new DisconnectedEventArgs { Error = new Exception("Dummy Message") }); } - private void StartTestRunErrorTestsTemplate(string errorMessage, Action onClientProcessExitCallback) + private void SetupDeserializeMessage(string messageType, TPayload payload) { - var mockHandler = new Mock(); - var runCriteria = new TestRunCriteriaWithSources(null, null, null, null); - this.mockCommunicationManager.Setup(mc => mc.ReceiveRawMessageAsync(It.IsAny())) - .Callback(() => onClientProcessExitCallback(errorMessage)).Returns(Task.FromResult((string)null)); - this.mockCommunicationManager.Setup(mc => mc.HostServer(It.IsAny())).Returns(new IPEndPoint(IPAddress.Loopback, 0)); - string testCompleteRawMessage = - "{\"MessageType\":\"TestExecution.Completed\",\"Payload\":{\"TestRunCompleteArgs\":{\"TestRunStatistics\":null,\"IsCanceled\":false,\"IsAborted\":true,\"Error\":{\"ClassName\":\"System.IO.IOException\",\"Message\":\"Unable to read data from the transport connection: An existing connection was forcibly closed by the remote host.\",\"Data\":null,\"InnerException\":null},\"AttachmentSets\":null,\"ElapsedTimeInRunningTests\":\"00:00:00\"},\"LastRunTests\":null,\"RunAttachments\":null,\"ExecutorUris\":null}}"; - this.mockDataSerializer.Setup( - md => md.SerializePayload(MessageType.ExecutionComplete, It.IsAny())) - .Returns(testCompleteRawMessage); - var waitHandle = new AutoResetEvent(false); - mockHandler.Setup(mh => mh.HandleTestRunComplete(It.IsAny(), null, null, null)).Callback(() => waitHandle.Set()); + this.mockDataSerializer.Setup(ds => ds.DeserializeMessage(It.IsAny())) + .Returns(new Message { MessageType = messageType }); + this.mockDataSerializer.Setup(ds => ds.DeserializePayload(It.IsAny())) + .Returns(payload); + } - this.testRequestSender.InitializeCommunication(); - this.testRequestSender.StartTestRun(runCriteria, mockHandler.Object); - waitHandle.WaitOne(); - this.testRequestSender.EndSession(); + private void SetupExceptionMessageSerialize() + { + // Serialize the exception message + this.mockDataSerializer + .Setup(ds => ds.SerializePayload(MessageType.TestMessage, It.Is(p => p.Message.Contains("Dummy Message")))) + .Returns("SerializedMessage"); + } + + private void SetupOperationAbortedPayload() + { + // Serialize the execution aborted + this.mockDataSerializer + .Setup(ds => ds.SerializePayload(MessageType.ExecutionComplete, It.Is(p => p.TestRunCompleteArgs.IsAborted))) + .Returns("SerializedAbortedPayload"); + } + + private void SetupExceptionOnMessageReceived() + { + this.SetupExceptionMessageSerialize(); + this.SetupOperationAbortedPayload(); - mockHandler.Verify(mh => mh.HandleLogMessage(TestMessageLevel.Error, string.Format(CommunicationUtilitiesResources.AbortedTestRun, errorMessage)), Times.Once); - mockHandler.Verify(mh => mh.HandleTestRunComplete(It.IsAny(), null, null, null), Times.Once); - mockHandler.Verify(mh => mh.HandleRawMessage(testCompleteRawMessage), Times.Once); - this.mockCommunicationManager.Verify(mc => mc.SendMessage(MessageType.SessionEnd), Times.Never); + this.mockDataSerializer.Setup(ds => ds.DeserializeMessage(It.IsAny())) + .Callback(() => throw new Exception("Dummy Message")); + } + + private void SetupRaiseMessageReceivedOnCheckVersion() + { + this.mockChannel.Setup(mc => mc.Send(It.IsAny())).Callback(this.RaiseMessageReceivedEvent); + } + + private void ResetRaiseMessageReceivedOnCheckVersion() + { + this.mockChannel.Reset(); + } + + private class TestableTestRequestSender : TestRequestSender + { + public TestableTestRequestSender(ICommunicationEndPoint commEndpoint, TestHostConnectionInfo connectionInfo, IDataSerializer serializer, ProtocolConfig protocolConfig) + : base(commEndpoint, connectionInfo, serializer, protocolConfig, 0) + { + } } } } diff --git a/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/TestRequestSenderTests2.cs b/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/TestRequestSenderTests2.cs deleted file mode 100644 index 276e8fbbb1..0000000000 --- a/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/TestRequestSenderTests2.cs +++ /dev/null @@ -1,779 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -namespace Microsoft.TestPlatform.CommunicationUtilities.UnitTests -{ - using System; - using System.Collections.Generic; - using System.Linq; - - using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities; - using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.Interfaces; - using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.ObjectModel; - using Microsoft.VisualStudio.TestPlatform.ObjectModel; - using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; - using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; - using Microsoft.VisualStudio.TestTools.UnitTesting; - using Moq; - - [TestClass] - public class TestRequestSenderTests2 - { - private const int DUMMYPROTOCOLVERSION = 42; - private const int DEFAULTPROTOCOLVERSION = 1; - private const int DUMMYNEGOTIATEDPROTOCOLVERSION = 41; - - private readonly ITestRequestSender testRequestSender; - private readonly Mock mockServer; - private readonly Mock mockDataSerializer; - private readonly Mock mockChannel; - - private readonly ConnectedEventArgs connectedEventArgs; - private readonly List pathToAdditionalExtensions = new List { "Hello", "World" }; - private readonly Mock mockDiscoveryEventsHandler; - private readonly Mock mockExecutionEventsHandler; - private readonly TestRunCriteriaWithSources testRunCriteriaWithSources; - - public TestRequestSenderTests2() - { - this.mockChannel = new Mock(); - this.mockServer = new Mock(); - this.mockDataSerializer = new Mock(); - this.testRequestSender = new TestableTestRequestSender(this.mockServer.Object, this.mockDataSerializer.Object, new ProtocolConfig { Version = DUMMYPROTOCOLVERSION }); - - this.connectedEventArgs = new ConnectedEventArgs(this.mockChannel.Object); - this.mockDiscoveryEventsHandler = new Mock(); - this.mockExecutionEventsHandler = new Mock(); - this.testRunCriteriaWithSources = new TestRunCriteriaWithSources(new Dictionary>(), null, "runsettings", null); - } - - [TestMethod] - public void InitializeCommunicationShouldHostServerAndAcceptClient() - { - var port = this.SetupFakeCommunicationChannel(); - - this.mockServer.Verify(mc => mc.Start(), Times.Once); - Assert.AreEqual(port, "123", "Correct port must be returned."); - } - - [TestMethod] - public void WaitForRequestHandlerConnectionShouldWaitForClientToConnect() - { - this.SetupFakeCommunicationChannel(); - - var connected = this.testRequestSender.WaitForRequestHandlerConnection(1); - - Assert.IsTrue(connected); - } - - [TestMethod] - public void CloseShouldCallStopServerOnCommunicationManager() - { - this.testRequestSender.Close(); - - this.mockServer.Verify(mc => mc.Stop(), Times.Once); - } - - [TestMethod] - public void DisposeShouldCallStopServerOnCommunicationManager() - { - this.testRequestSender.Dispose(); - - this.mockServer.Verify(mc => mc.Stop(), Times.Once); - } - - [TestMethod] - public void EndSessionShouldSendSessionEndMessage() - { - this.SetupFakeCommunicationChannel(); - - this.testRequestSender.EndSession(); - - this.mockDataSerializer.Verify(ds => ds.SerializeMessage(MessageType.SessionEnd), Times.Once); - this.mockChannel.Verify(c => c.Send(It.IsAny()), Times.Once); - } - - [TestMethod] - public void EndSessionShouldNotSendSessionEndMessageIfClientDisconnected() - { - this.SetupFakeCommunicationChannel(); - this.testRequestSender.DiscoverTests(new DiscoveryCriteria(), this.mockDiscoveryEventsHandler.Object); - this.RaiseClientDisconnectedEvent(); - - this.testRequestSender.EndSession(); - - this.mockDataSerializer.Verify(ds => ds.SerializeMessage(MessageType.SessionEnd), Times.Never); - } - - [TestMethod] - public void EndSessionShouldNotSendSessionEndMessageIfTestHostProcessExited() - { - this.SetupFakeCommunicationChannel(); - this.testRequestSender.StartTestRun(this.testRunCriteriaWithSources, this.mockExecutionEventsHandler.Object); - this.testRequestSender.OnClientProcessExit("Dummy Message"); - - this.testRequestSender.EndSession(); - - this.mockDataSerializer.Verify(ds => ds.SerializeMessage(MessageType.SessionEnd), Times.Once); - } - - [DataTestMethod] - [DataRow("")] - [DataRow(" ")] - [DataRow(null)] - public void OnClientProcessExitShouldSendErrorMessageIfStdErrIsEmpty(string stderr) - { - this.SetupFakeCommunicationChannel(); - this.testRequestSender.DiscoverTests(new DiscoveryCriteria(), this.mockDiscoveryEventsHandler.Object); - - this.testRequestSender.OnClientProcessExit(stderr); - - var expectedErrorMessage = "Reason: " + stderr; - this.RaiseClientDisconnectedEvent(); - this.mockDiscoveryEventsHandler.Verify(eh => eh.HandleLogMessage(TestMessageLevel.Error, It.Is(s => s.EndsWith(expectedErrorMessage))), Times.Once); - } - - [TestMethod] - public void OnClientProcessExitShouldNotSendErrorMessageIfOperationNotStarted() - { - this.SetupFakeCommunicationChannel(); - - this.testRequestSender.OnClientProcessExit("Dummy Stderr"); - - this.RaiseClientDisconnectedEvent(); - this.mockDiscoveryEventsHandler.Verify(eh => eh.HandleLogMessage(TestMessageLevel.Error, It.Is(s => s.Contains("Dummy Stderr"))), Times.Never); - } - - [TestMethod] - public void OnClientProcessExitShouldNotSendRawMessageIfOperationNotStarted() - { - this.SetupFakeCommunicationChannel(); - - this.testRequestSender.OnClientProcessExit("Dummy Stderr"); - - this.RaiseClientDisconnectedEvent(); - this.mockDataSerializer.Verify(ds => ds.SerializePayload(MessageType.TestMessage, It.Is(p => p.Message.Contains("Dummy Stderr"))), Times.Never); - this.mockExecutionEventsHandler.Verify(eh => eh.HandleRawMessage(It.IsAny()), Times.Never); - } - - #region Version Check Tests - - [TestMethod] - public void CheckVersionWithTestHostShouldSendHighestSupportedVersion() - { - this.SetupDeserializeMessage(MessageType.VersionCheck, 99); - this.SetupRaiseMessageReceivedOnCheckVersion(); - this.SetupFakeCommunicationChannel(); - - this.testRequestSender.CheckVersionWithTestHost(); - - this.mockDataSerializer.Verify(ds => ds.SerializePayload(MessageType.VersionCheck, DUMMYPROTOCOLVERSION), Times.Once); - this.mockChannel.Verify(mc => mc.Send(It.IsAny()), Times.Once); - } - - [TestMethod] - public void CheckVersionWithTestHostShouldThrowIfTestHostVersionDoesNotMatch() - { - this.SetupDeserializeMessage(MessageType.ProtocolError, string.Empty); - this.SetupRaiseMessageReceivedOnCheckVersion(); - this.SetupFakeCommunicationChannel(); - - Assert.ThrowsException(() => this.testRequestSender.CheckVersionWithTestHost()); - } - - [TestMethod] - public void CheckVersionWithTestHostShouldThrowIfUnexpectedResponseIsReceived() - { - this.SetupDeserializeMessage(MessageType.TestCasesFound, string.Empty); - this.SetupRaiseMessageReceivedOnCheckVersion(); - this.SetupFakeCommunicationChannel(); - - Assert.ThrowsException(() => this.testRequestSender.CheckVersionWithTestHost()); - } - - #endregion - - #region Discovery Protocol Tests - [TestMethod] - public void InitializeDiscoveryShouldSendCommunicationMessageWithCorrectParameters() - { - this.SetupFakeCommunicationChannel(); - - this.testRequestSender.InitializeDiscovery(this.pathToAdditionalExtensions); - - this.mockDataSerializer.Verify(d => d.SerializePayload(MessageType.DiscoveryInitialize, this.pathToAdditionalExtensions, 1), Times.Once); - this.mockChannel.Verify(mc => mc.Send(It.IsAny()), Times.Once); - } - - [TestMethod] - public void InitializeDiscoveryShouldSendCommunicationMessageWithCorrectParametersWithVersion() - { - this.SetupFakeChannelWithVersionNegotiation(DUMMYNEGOTIATEDPROTOCOLVERSION); - - this.testRequestSender.InitializeDiscovery(this.pathToAdditionalExtensions); - - this.mockDataSerializer.Verify(d => d.SerializePayload(MessageType.DiscoveryInitialize, this.pathToAdditionalExtensions, DUMMYNEGOTIATEDPROTOCOLVERSION), Times.Once); - } - - [TestMethod] - public void DiscoverTestsShouldSendStartDiscoveryMessageOnChannel() - { - this.SetupFakeCommunicationChannel(); - - this.testRequestSender.DiscoverTests(new DiscoveryCriteria(), this.mockDiscoveryEventsHandler.Object); - - this.mockDataSerializer.Verify( - s => s.SerializePayload(MessageType.StartDiscovery, It.IsAny(), DEFAULTPROTOCOLVERSION), - Times.Once); - this.mockChannel.Verify(mc => mc.Send(It.IsAny()), Times.Once); - } - - [TestMethod] - public void DiscoverTestsShouldSendStartDiscoveryMessageOnChannelWithVersion() - { - this.SetupFakeChannelWithVersionNegotiation(DUMMYNEGOTIATEDPROTOCOLVERSION); - - this.testRequestSender.DiscoverTests(new DiscoveryCriteria(), this.mockDiscoveryEventsHandler.Object); - - this.mockDataSerializer.Verify( - s => s.SerializePayload(MessageType.StartDiscovery, It.IsAny(), DUMMYNEGOTIATEDPROTOCOLVERSION), - Times.Once); - } - - [TestMethod] - public void DiscoverTestsShouldNotifyRawMessageOnMessageReceived() - { - this.SetupDeserializeMessage(MessageType.TestMessage, new TestMessagePayload()); - this.SetupFakeCommunicationChannel(); - - this.testRequestSender.DiscoverTests(new DiscoveryCriteria(), this.mockDiscoveryEventsHandler.Object); - - this.RaiseMessageReceivedEvent(); - this.mockDiscoveryEventsHandler.Verify(eh => eh.HandleRawMessage("DummyData"), Times.Once); - } - - [TestMethod] - public void DiscoverTestsShouldNotifyDiscoveredTestsOnTestCasesFoundMessageReceived() - { - this.SetupDeserializeMessage>(MessageType.TestCasesFound, new TestCase[2]); - this.SetupFakeCommunicationChannel(); - - this.testRequestSender.DiscoverTests(new DiscoveryCriteria(), this.mockDiscoveryEventsHandler.Object); - - this.RaiseMessageReceivedEvent(); - this.mockDiscoveryEventsHandler.Verify(eh => eh.HandleDiscoveredTests(It.Is>(t => t.Count() == 2))); - } - - [TestMethod] - public void DiscoverTestsShouldNotifyDiscoveryCompleteOnCompleteMessageReceived() - { - var completePayload = new DiscoveryCompletePayload { TotalTests = 10, IsAborted = false }; - this.SetupDeserializeMessage(MessageType.DiscoveryComplete, completePayload); - this.SetupFakeCommunicationChannel(); - - this.testRequestSender.DiscoverTests(new DiscoveryCriteria(), this.mockDiscoveryEventsHandler.Object); - - this.RaiseMessageReceivedEvent(); - this.mockDiscoveryEventsHandler.Verify(eh => eh.HandleDiscoveryComplete(It.IsAny(), null)); - } - - [TestMethod] - public void DiscoverTestShouldNotifyLogMessageOnTestMessageReceived() - { - var message = new TestMessagePayload { MessageLevel = TestMessageLevel.Error, Message = "Message1" }; - this.SetupDeserializeMessage(MessageType.TestMessage, message); - this.SetupFakeCommunicationChannel(); - - this.testRequestSender.DiscoverTests(new DiscoveryCriteria(), this.mockDiscoveryEventsHandler.Object); - - this.RaiseMessageReceivedEvent(); - this.mockDiscoveryEventsHandler.Verify(eh => eh.HandleLogMessage(TestMessageLevel.Error, "Message1")); - } - - [TestMethod] - public void DiscoverTestShouldNotifyLogMessageIfExceptionThrownOnMessageReceived() - { - this.SetupExceptionOnMessageReceived(); - this.SetupFakeCommunicationChannel(); - - this.testRequestSender.DiscoverTests(new DiscoveryCriteria(), this.mockDiscoveryEventsHandler.Object); - - this.RaiseMessageReceivedEvent(); - this.mockDiscoveryEventsHandler.Verify(eh => eh.HandleLogMessage(TestMessageLevel.Error, It.Is(s => s.Contains("Dummy Message")))); - this.mockDiscoveryEventsHandler.Verify(eh => eh.HandleRawMessage("SerializedMessage"), Times.Once); - } - - [TestMethod] - public void DiscoverTestShouldNotifyDiscoveryCompleteIfExceptionThrownOnMessageReceived() - { - this.SetupExceptionOnMessageReceived(); - this.SetupFakeCommunicationChannel(); - - this.testRequestSender.DiscoverTests(new DiscoveryCriteria(), this.mockDiscoveryEventsHandler.Object); - - this.RaiseMessageReceivedEvent(); - this.mockDiscoveryEventsHandler.Verify(eh => eh.HandleDiscoveryComplete(It.IsAny(), null)); - } - - [TestMethod] - public void DiscoverTestsShouldNotAbortDiscoveryIfClientDisconnectedAndOperationIsComplete() - { - var completePayload = new DiscoveryCompletePayload { TotalTests = 10, IsAborted = false }; - this.SetupDeserializeMessage(MessageType.DiscoveryComplete, completePayload); - this.SetupFakeCommunicationChannel(); - this.testRequestSender.DiscoverTests(new DiscoveryCriteria(), this.mockDiscoveryEventsHandler.Object); - this.RaiseMessageReceivedEvent(); // Raise discovery complete - - this.RaiseClientDisconnectedEvent(); - - this.mockDiscoveryEventsHandler.Verify(eh => eh.HandleLogMessage(TestMessageLevel.Error, It.IsAny()), Times.Never); - this.mockDiscoveryEventsHandler.Verify(eh => eh.HandleDiscoveryComplete(It.IsAny(), null), Times.Once); - } - - [TestMethod] - public void DiscoverTestShouldNotifyLogMessageIfClientDisconnected() - { - // Expect default error message since we've not set any client exit message - var expectedErrorMessage = "Reason: Unable to communicate"; - this.SetupFakeCommunicationChannel(); - this.mockDataSerializer.Setup(ds => ds.SerializePayload(MessageType.TestMessage, It.Is(p => p.Message.Contains(expectedErrorMessage)))) - .Returns("Serialized error"); - this.testRequestSender.DiscoverTests(new DiscoveryCriteria(), this.mockDiscoveryEventsHandler.Object); - - this.RaiseClientDisconnectedEvent(); - - this.mockDiscoveryEventsHandler.Verify(eh => eh.HandleLogMessage(TestMessageLevel.Error, It.Is(s => s.Contains(expectedErrorMessage)))); - this.mockDiscoveryEventsHandler.Verify(eh => eh.HandleRawMessage(It.Is(s => !string.IsNullOrEmpty(s) && s.Equals("Serialized error"))), Times.Once); - } - - [TestMethod] - public void DiscoverTestShouldNotifyLogMessageIfClientDisconnectedWithClientExit() - { - this.SetupFakeCommunicationChannel(); - this.mockDataSerializer.Setup(ds => ds.SerializePayload(MessageType.TestMessage, It.Is(p => p.Message.Contains("Dummy Stderr")))) - .Returns("Serialized Stderr"); - this.testRequestSender.DiscoverTests(new DiscoveryCriteria(), this.mockDiscoveryEventsHandler.Object); - this.testRequestSender.OnClientProcessExit("Dummy Stderr"); - - this.RaiseClientDisconnectedEvent(); - - this.mockDiscoveryEventsHandler.Verify(eh => eh.HandleLogMessage(TestMessageLevel.Error, It.Is(s => s.Contains("Dummy Stderr"))), Times.Once); - this.mockDiscoveryEventsHandler.Verify(eh => eh.HandleRawMessage(It.Is(s => !string.IsNullOrEmpty(s) && s.Equals("Serialized Stderr"))), Times.Once); - } - - [TestMethod] - public void DiscoverTestShouldNotifyDiscoveryCompleteIfClientDisconnected() - { - this.SetupFakeCommunicationChannel(); - this.testRequestSender.DiscoverTests(new DiscoveryCriteria(), this.mockDiscoveryEventsHandler.Object); - - this.RaiseClientDisconnectedEvent(); - - this.mockDiscoveryEventsHandler.Verify(eh => eh.HandleDiscoveryComplete(It.IsAny(), null)); - } - - [TestMethod] - public void DiscoverTestsShouldCollectMetricsOnHandleDiscoveryComplete() - { - var dict = new Dictionary(); - dict.Add("DummyMessage", "DummyValue"); - - var mockHandler = new Mock(); - var completePayload = new DiscoveryCompletePayload() - { - IsAborted = false, - LastDiscoveredTests = null, - TotalTests = 1, - Metrics = dict - }; - this.SetupDeserializeMessage(MessageType.DiscoveryComplete, completePayload); - this.SetupFakeCommunicationChannel(); - - DiscoveryCompleteEventArgs actualDiscoveryCompleteEventArgs = null; - mockHandler.Setup(md => md.HandleDiscoveryComplete(It.IsAny(), null)) - .Callback>( - (discoveryCompleteEventArgs, testCase) => - { - actualDiscoveryCompleteEventArgs = discoveryCompleteEventArgs; - }); - - // Act. - this.testRequestSender.DiscoverTests(new DiscoveryCriteria(), mockHandler.Object); - this.RaiseMessageReceivedEvent(); - - // Verify - Assert.AreEqual(actualDiscoveryCompleteEventArgs.Metrics, dict); - } - - #endregion - - #region Execution Protocol Tests - - [TestMethod] - public void InitializeExecutionShouldSendCommunicationMessageWithCorrectParameters() - { - this.SetupFakeCommunicationChannel(); - - this.testRequestSender.InitializeExecution(this.pathToAdditionalExtensions); - - this.mockDataSerializer.Verify(d => d.SerializePayload(MessageType.ExecutionInitialize, this.pathToAdditionalExtensions, 1), Times.Once); - this.mockChannel.Verify(mc => mc.Send(It.IsAny()), Times.Once); - } - - [TestMethod] - public void InitializeExecutionShouldSendCommunicationMessageWithCorrectParametersWithVersion() - { - this.SetupFakeChannelWithVersionNegotiation(DUMMYNEGOTIATEDPROTOCOLVERSION); - - this.testRequestSender.InitializeExecution(this.pathToAdditionalExtensions); - - this.mockDataSerializer.Verify(d => d.SerializePayload(MessageType.ExecutionInitialize, this.pathToAdditionalExtensions, DUMMYNEGOTIATEDPROTOCOLVERSION), Times.Once); - } - - [TestMethod] - public void StartTestRunShouldSendStartTestExecutionWithSourcesOnChannel() - { - this.SetupFakeCommunicationChannel(); - - this.testRequestSender.StartTestRun(this.testRunCriteriaWithSources, this.mockExecutionEventsHandler.Object); - - this.mockDataSerializer.Verify(d => d.SerializePayload(MessageType.StartTestExecutionWithSources, this.testRunCriteriaWithSources, DEFAULTPROTOCOLVERSION), Times.Once); - this.mockChannel.Verify(mc => mc.Send(It.IsAny()), Times.Once); - } - - [TestMethod] - public void StartTestRunShouldSendStartTestExecutionWithSourcesOnChannelWithVersion() - { - this.SetupFakeChannelWithVersionNegotiation(DUMMYNEGOTIATEDPROTOCOLVERSION); - - this.testRequestSender.StartTestRun(this.testRunCriteriaWithSources, this.mockExecutionEventsHandler.Object); - - this.mockDataSerializer.Verify(d => d.SerializePayload(MessageType.StartTestExecutionWithSources, this.testRunCriteriaWithSources, DUMMYNEGOTIATEDPROTOCOLVERSION), Times.Once); - } - - [TestMethod] - public void StartTestRunWithTestsShouldSendStartTestExecutionWithTestsOnChannel() - { - var runCriteria = new TestRunCriteriaWithTests(new TestCase[2], null, "runsettings", null); - this.SetupFakeCommunicationChannel(); - - this.testRequestSender.StartTestRun(runCriteria, this.mockExecutionEventsHandler.Object); - - this.mockDataSerializer.Verify(d => d.SerializePayload(MessageType.StartTestExecutionWithTests, runCriteria, DEFAULTPROTOCOLVERSION), Times.Once); - this.mockChannel.Verify(mc => mc.Send(It.IsAny()), Times.Once); - } - - [TestMethod] - public void StartTestRunWithTestsShouldSendStartTestExecutionWithTestsOnChannelWithVersion() - { - var runCriteria = new TestRunCriteriaWithTests(new TestCase[2], null, "runsettings", null); - this.SetupFakeChannelWithVersionNegotiation(DUMMYNEGOTIATEDPROTOCOLVERSION); - - this.testRequestSender.StartTestRun(runCriteria, this.mockExecutionEventsHandler.Object); - - this.mockDataSerializer.Verify(d => d.SerializePayload(MessageType.StartTestExecutionWithTests, runCriteria, DUMMYNEGOTIATEDPROTOCOLVERSION), Times.Once); - } - - [TestMethod] - public void StartTestRunShouldNotifyRawMessageOnMessageReceived() - { - this.SetupDeserializeMessage(MessageType.TestMessage, new TestMessagePayload()); - this.SetupFakeCommunicationChannel(); - - this.testRequestSender.StartTestRun(this.testRunCriteriaWithSources, this.mockExecutionEventsHandler.Object); - - this.RaiseMessageReceivedEvent(); - this.mockExecutionEventsHandler.Verify(eh => eh.HandleRawMessage("DummyData"), Times.Once); - } - - [TestMethod] - public void StartTestRunShouldNotifyTestRunStatsChangeOnRunStatsMessageReceived() - { - var testRunChangedArgs = new TestRunChangedEventArgs( - null, - new Microsoft.VisualStudio.TestPlatform.ObjectModel.TestResult[2], - new TestCase[2]); - this.SetupDeserializeMessage(MessageType.TestRunStatsChange, testRunChangedArgs); - this.SetupFakeCommunicationChannel(); - - this.testRequestSender.StartTestRun(this.testRunCriteriaWithSources, this.mockExecutionEventsHandler.Object); - - this.RaiseMessageReceivedEvent(); - this.mockExecutionEventsHandler.Verify(eh => eh.HandleTestRunStatsChange(testRunChangedArgs), Times.Once); - } - - [TestMethod] - public void StartTestRunShouldNotifyExecutionCompleteOnRunCompleteMessageReceived() - { - var testRunCompletePayload = new TestRunCompletePayload - { - TestRunCompleteArgs = new TestRunCompleteEventArgs(null, false, false, null, null, TimeSpan.MaxValue), - LastRunTests = new TestRunChangedEventArgs(null, null, null), - RunAttachments = new List() - }; - this.SetupDeserializeMessage(MessageType.ExecutionComplete, testRunCompletePayload); - this.SetupFakeCommunicationChannel(); - - this.testRequestSender.StartTestRun(this.testRunCriteriaWithSources, this.mockExecutionEventsHandler.Object); - - this.RaiseMessageReceivedEvent(); - this.mockExecutionEventsHandler.Verify( - eh => eh.HandleTestRunComplete( - testRunCompletePayload.TestRunCompleteArgs, - testRunCompletePayload.LastRunTests, - testRunCompletePayload.RunAttachments, - It.IsAny>()), - Times.Once); - } - - [TestMethod] - public void StartTestRunShouldNotifyLogMessageOnTestMessageReceived() - { - var testMessagePayload = new TestMessagePayload { MessageLevel = TestMessageLevel.Error, Message = "Dummy" }; - this.SetupDeserializeMessage(MessageType.TestMessage, testMessagePayload); - this.SetupFakeCommunicationChannel(); - - this.testRequestSender.StartTestRun(this.testRunCriteriaWithSources, this.mockExecutionEventsHandler.Object); - - this.RaiseMessageReceivedEvent(); - this.mockExecutionEventsHandler.Verify(eh => eh.HandleLogMessage(TestMessageLevel.Error, "Dummy"), Times.Once); - } - - [TestMethod] - public void StartTestRunShouldNotifyLaunchWithDebuggerOnMessageReceived() - { - var launchMessagePayload = new TestProcessStartInfo(); - this.SetupDeserializeMessage(MessageType.LaunchAdapterProcessWithDebuggerAttached, launchMessagePayload); - this.SetupFakeCommunicationChannel(); - - this.testRequestSender.StartTestRun(this.testRunCriteriaWithSources, this.mockExecutionEventsHandler.Object); - - this.RaiseMessageReceivedEvent(); - this.mockExecutionEventsHandler.Verify(eh => eh.LaunchProcessWithDebuggerAttached(launchMessagePayload), Times.Once); - } - - [TestMethod] - public void StartTestRunShouldSendLaunchDebuggerAttachedCallbackOnMessageReceived() - { - var launchMessagePayload = new TestProcessStartInfo(); - this.SetupDeserializeMessage(MessageType.LaunchAdapterProcessWithDebuggerAttached, launchMessagePayload); - this.SetupFakeCommunicationChannel(); - - this.testRequestSender.StartTestRun(this.testRunCriteriaWithSources, this.mockExecutionEventsHandler.Object); - - this.RaiseMessageReceivedEvent(); - this.mockDataSerializer.Verify(ds => ds.SerializePayload(MessageType.LaunchAdapterProcessWithDebuggerAttachedCallback, It.IsAny(), 1), Times.Once); - this.mockChannel.Verify(c => c.Send(It.IsAny()), Times.AtLeastOnce); - } - - [TestMethod] - public void StartTestRunShouldSendLaunchDebuggerAttachedCallbackOnMessageReceivedWithVersion() - { - var launchMessagePayload = new TestProcessStartInfo(); - this.SetupFakeChannelWithVersionNegotiation(DUMMYNEGOTIATEDPROTOCOLVERSION); - this.SetupDeserializeMessage(MessageType.LaunchAdapterProcessWithDebuggerAttached, launchMessagePayload); - - this.testRequestSender.StartTestRun(this.testRunCriteriaWithSources, this.mockExecutionEventsHandler.Object); - - this.RaiseMessageReceivedEvent(); - this.mockDataSerializer.Verify(ds => ds.SerializePayload(MessageType.LaunchAdapterProcessWithDebuggerAttachedCallback, It.IsAny(), DUMMYNEGOTIATEDPROTOCOLVERSION), Times.Once); - } - - [TestMethod] - public void StartTestRunShouldNotifyLogMessageIfExceptionIsThrownOnMessageReceived() - { - this.SetupExceptionOnMessageReceived(); - this.SetupFakeCommunicationChannel(); - - this.testRequestSender.StartTestRun(this.testRunCriteriaWithSources, this.mockExecutionEventsHandler.Object); - - this.RaiseMessageReceivedEvent(); - this.mockExecutionEventsHandler.Verify(eh => eh.HandleLogMessage(TestMessageLevel.Error, It.Is(s => s.Contains("Dummy Message"))), Times.Once); - this.mockExecutionEventsHandler.Verify(eh => eh.HandleRawMessage("SerializedMessage"), Times.Once); - } - - [TestMethod] - public void StartTestRunShouldNotifyExecutionCompleteIfExceptionIsThrownOnMessageReceived() - { - this.SetupExceptionOnMessageReceived(); - this.SetupFakeCommunicationChannel(); - - this.testRequestSender.StartTestRun(this.testRunCriteriaWithSources, this.mockExecutionEventsHandler.Object); - - this.RaiseMessageReceivedEvent(); - this.mockExecutionEventsHandler.Verify(eh => eh.HandleTestRunComplete(It.Is(t => t.IsAborted), null, null, null), Times.Once); - this.mockExecutionEventsHandler.Verify(eh => eh.HandleRawMessage("SerializedAbortedPayload"), Times.Once); - } - - [TestMethod] - public void StartTestRunShouldNotNotifyExecutionCompleteIfClientDisconnectedAndOperationComplete() - { - var testRunCompletePayload = new TestRunCompletePayload - { - TestRunCompleteArgs = new TestRunCompleteEventArgs(null, false, false, null, null, TimeSpan.MaxValue), - LastRunTests = new TestRunChangedEventArgs(null, null, null), - RunAttachments = new List() - }; - this.SetupDeserializeMessage(MessageType.ExecutionComplete, testRunCompletePayload); - this.SetupFakeCommunicationChannel(); - this.testRequestSender.StartTestRun(this.testRunCriteriaWithSources, this.mockExecutionEventsHandler.Object); - this.RaiseMessageReceivedEvent(); // Raise test run complete - - this.RaiseClientDisconnectedEvent(); - - this.mockExecutionEventsHandler.Verify(eh => eh.HandleTestRunComplete(It.Is(t => t.IsAborted), null, null, null), Times.Never); - this.mockExecutionEventsHandler.Verify(eh => eh.HandleRawMessage("SerializedAbortedPayload"), Times.Never); - } - - [TestMethod] - public void StartTestRunShouldNotifyErrorLogMessageIfClientDisconnected() - { - this.SetupFakeCommunicationChannel(); - this.testRequestSender.StartTestRun(this.testRunCriteriaWithSources, this.mockExecutionEventsHandler.Object); - - this.RaiseClientDisconnectedEvent(); - - // Expect default error message since we've not set any client exit message - var expectedErrorMessage = "Reason: Unable to communicate"; - this.mockExecutionEventsHandler.Verify(eh => eh.HandleLogMessage(TestMessageLevel.Error, It.Is(s => s.Contains(expectedErrorMessage))), Times.Once); - } - - [TestMethod] - public void StartTestRunShouldNotifyErrorLogMessageIfClientDisconnectedWithClientExit() - { - this.SetupFakeCommunicationChannel(); - this.testRequestSender.StartTestRun(this.testRunCriteriaWithSources, this.mockExecutionEventsHandler.Object); - this.testRequestSender.OnClientProcessExit("Dummy Stderr"); - - this.RaiseClientDisconnectedEvent(); - - var expectedErrorMessage = "Reason: Dummy Stderr"; - this.mockExecutionEventsHandler.Verify(eh => eh.HandleLogMessage(TestMessageLevel.Error, It.Is(s => s.Contains(expectedErrorMessage))), Times.Once); - } - - [TestMethod] - public void StartTestRunShouldNotifyExecutionCompleteIfClientDisconnected() - { - this.SetupOperationAbortedPayload(); - this.SetupFakeCommunicationChannel(); - this.testRequestSender.StartTestRun(this.testRunCriteriaWithSources, this.mockExecutionEventsHandler.Object); - - this.RaiseClientDisconnectedEvent(); - - this.mockExecutionEventsHandler.Verify(eh => eh.HandleTestRunComplete(It.Is(t => t.IsAborted), null, null, null), Times.Once); - this.mockExecutionEventsHandler.Verify(eh => eh.HandleRawMessage("SerializedAbortedPayload"), Times.Once); - } - - [TestMethod] - public void SendTestRunCancelShouldSendCancelTestRunMessage() - { - this.SetupFakeCommunicationChannel(); - - this.testRequestSender.SendTestRunCancel(); - - this.mockDataSerializer.Verify(ds => ds.SerializeMessage(MessageType.CancelTestRun), Times.Once); - this.mockChannel.Verify(c => c.Send(It.IsAny()), Times.Once); - } - - [TestMethod] - public void SendTestRunAbortShouldSendAbortTestRunMessage() - { - this.SetupFakeCommunicationChannel(); - - this.testRequestSender.SendTestRunAbort(); - - this.mockDataSerializer.Verify(ds => ds.SerializeMessage(MessageType.AbortTestRun), Times.Once); - this.mockChannel.Verify(c => c.Send(It.IsAny()), Times.Once); - } - - #endregion - - private string SetupFakeCommunicationChannel(string connectionArgs = "123") - { - // Setup mock connected event and initialize communication channel - this.mockServer.Setup(mc => mc.Start()) - .Returns(connectionArgs) - .Callback(() => this.mockServer.Raise(s => s.ClientConnected += null, this.mockServer.Object, this.connectedEventArgs)); - - return this.testRequestSender.InitializeCommunication().ToString(); - } - - private void SetupFakeChannelWithVersionNegotiation(int protocolVersion) - { - // Sends a check version message to setup the negotiated protocol version. - // This method is only required in specific tests. - this.SetupDeserializeMessage(MessageType.VersionCheck, DUMMYNEGOTIATEDPROTOCOLVERSION); - this.SetupRaiseMessageReceivedOnCheckVersion(); - this.SetupFakeCommunicationChannel(); - this.testRequestSender.CheckVersionWithTestHost(); - this.ResetRaiseMessageReceivedOnCheckVersion(); - } - - private void RaiseMessageReceivedEvent() - { - this.mockChannel.Raise( - c => c.MessageReceived += null, - this.mockChannel.Object, - new MessageReceivedEventArgs { Data = "DummyData" }); - } - - private void RaiseClientDisconnectedEvent() - { - this.mockServer.Raise( - s => s.ClientDisconnected += null, - this.mockServer.Object, - new DisconnectedEventArgs { Error = new Exception("Dummy Message") }); - } - - private void SetupDeserializeMessage(string messageType, TPayload payload) - { - this.mockDataSerializer.Setup(ds => ds.DeserializeMessage(It.IsAny())) - .Returns(new Message { MessageType = messageType }); - this.mockDataSerializer.Setup(ds => ds.DeserializePayload(It.IsAny())) - .Returns(payload); - } - - private void SetupExceptionMessageSerialize() - { - // Serialize the exception message - this.mockDataSerializer - .Setup(ds => ds.SerializePayload(MessageType.TestMessage, It.Is(p => p.Message.Contains("Dummy Message")))) - .Returns("SerializedMessage"); - } - - private void SetupOperationAbortedPayload() - { - // Serialize the execution aborted - this.mockDataSerializer - .Setup(ds => ds.SerializePayload(MessageType.ExecutionComplete, It.Is(p => p.TestRunCompleteArgs.IsAborted))) - .Returns("SerializedAbortedPayload"); - } - - private void SetupExceptionOnMessageReceived() - { - this.SetupExceptionMessageSerialize(); - this.SetupOperationAbortedPayload(); - - this.mockDataSerializer.Setup(ds => ds.DeserializeMessage(It.IsAny())) - .Callback(() => throw new Exception("Dummy Message")); - } - - private void SetupRaiseMessageReceivedOnCheckVersion() - { - this.mockChannel.Setup(mc => mc.Send(It.IsAny())).Callback(this.RaiseMessageReceivedEvent); - } - - private void ResetRaiseMessageReceivedOnCheckVersion() - { - this.mockChannel.Reset(); - } - - private class TestableTestRequestSender : TestRequestSender2 - { - public TestableTestRequestSender(ICommunicationServer server, IDataSerializer serializer, ProtocolConfig protocolConfig) - : base(server, serializer, protocolConfig, 0) - { - } - } - } -} diff --git a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/Parallel/ParallelProxyExecutionManagerTests.cs b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/Parallel/ParallelProxyExecutionManagerTests.cs index 0edf241c61..504381e9d8 100644 --- a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/Parallel/ParallelProxyExecutionManagerTests.cs +++ b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/Parallel/ParallelProxyExecutionManagerTests.cs @@ -157,9 +157,6 @@ public void HandlePartialRunCompleteShouldCreateNewProxyExecutionManagerIfDataCo var proxyDataCollectionManager = new ProxyExecutionManagerWithDataCollection(this.mockRequestData.Object, this.mockRequestSender.Object, this.mockTestHostManager.Object, this.mockDataCollectionManager.Object); var parallelExecutionManager = this.SetupExecutionManager(this.proxyManagerFunc, 2, setupTestCases: true); - parallelExecutionManager.StartTestRun(this.testRunCriteriaWithTests, this.mockHandler.Object); - Assert.IsTrue(this.executionCompleted.Wait(taskTimeout), "Test run not completed."); - this.proxyManagerFuncCalled = false; parallelExecutionManager.HandlePartialRunComplete(proxyDataCollectionManager, completeArgs, null, null, null); Assert.IsTrue(this.proxyManagerFuncCalled); @@ -173,9 +170,6 @@ public void HandlePartialRunCompleteShouldCreateNewProxyExecutionManagerIfIsAbor this.mockRequestSender = new Mock(); var parallelExecutionManager = this.SetupExecutionManager(this.proxyManagerFunc, 2, setupTestCases: true); - parallelExecutionManager.StartTestRun(this.testRunCriteriaWithTests, this.mockHandler.Object); - Assert.IsTrue(this.executionCompleted.Wait(taskTimeout), "Test run not completed."); - this.proxyManagerFuncCalled = false; var proxyExecutionManagerManager = new ProxyExecutionManager(this.mockRequestData.Object, this.mockRequestSender.Object, this.mockTestHostManager.Object); parallelExecutionManager.HandlePartialRunComplete(proxyExecutionManagerManager, completeArgs, null, null, null); @@ -361,7 +355,7 @@ private ParallelProxyExecutionManager SetupExecutionManager(Func proxyManagerFunc, int parallelLevel, bool setupTestCases) { - var parallelExecutionManager = new ParallelProxyExecutionManager(this.mockRequestData.Object, this.proxyManagerFunc, 2); + var parallelExecutionManager = new ParallelProxyExecutionManager(this.mockRequestData.Object, proxyManagerFunc, parallelLevel); if (setupTestCases) { diff --git a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/ProxyBaseManagerTests.cs b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/ProxyBaseManagerTests.cs new file mode 100644 index 0000000000..bba1a233f0 --- /dev/null +++ b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/ProxyBaseManagerTests.cs @@ -0,0 +1,122 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace TestPlatform.CrossPlatEngine.UnitTests.Client +{ + using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities; + using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.Interfaces; + using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Client; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Host; + using Microsoft.VisualStudio.TestTools.UnitTesting; + using Moq; + using System.Collections.Generic; + using System.Net; + using System.Threading; + using System.Threading.Tasks; + + [TestClass] + public class ProxyBaseManagerTests + { + private const int CLIENTPROCESSEXITWAIT = 10 * 1000; + private int clientConnectionTimeout = 400; + private Mock mockCommunicationEndpoint; + private ITestRequestSender testRequestSender; + + ProtocolConfig protocolConfig = new ProtocolConfig { Version = 2 }; + private readonly Mock mockRequestData; + protected readonly Mock mockTestHostManager; + protected Mock mockDataSerializer; + protected Mock mockChannel; + + public ProxyBaseManagerTests() + { + this.mockTestHostManager = new Mock(); + this.mockDataSerializer = new Mock(); + this.mockRequestData = new Mock(); + this.mockChannel = new Mock(); + + this.mockRequestData.Setup(rd => rd.MetricsCollection).Returns(new Mock().Object); + this.mockDataSerializer.Setup(mds => mds.DeserializeMessage(null)).Returns(new Message()); + this.mockDataSerializer.Setup(mds => mds.DeserializeMessage(string.Empty)).Returns(new Message()); + this.mockTestHostManager.SetupGet(th => th.Shared).Returns(true); + this.mockTestHostManager.Setup( + m => m.GetTestHostProcessStartInfo( + It.IsAny>(), + It.IsAny>(), + It.IsAny())) + .Returns(new TestProcessStartInfo()); + this.mockTestHostManager.Setup(tmh => tmh.LaunchTestHostAsync(It.IsAny(), It.IsAny())) + .Callback( + () => + { + this.mockTestHostManager.Raise(thm => thm.HostLaunched += null, new HostProviderEventArgs(string.Empty)); + }) + .Returns(Task.FromResult(true)); + } + + private void SetupAndInitializeTestRequestSender() + { + var connectionInfo = new TestHostConnectionInfo + { + Endpoint = IPAddress.Loopback + ":0", + Role = ConnectionRole.Client, + Transport = Transport.Sockets + }; + this.mockCommunicationEndpoint = new Mock(); + this.mockDataSerializer = new Mock(); + this.testRequestSender = new TestRequestSender(this.mockCommunicationEndpoint.Object, connectionInfo, this.mockDataSerializer.Object, this.protocolConfig, CLIENTPROCESSEXITWAIT); + this.mockCommunicationEndpoint.Setup(mc => mc.Start(connectionInfo.Endpoint)).Returns(connectionInfo.Endpoint).Callback(() => + { + this.mockCommunicationEndpoint.Raise( + s => s.Connected += null, + this.mockCommunicationEndpoint.Object, + new ConnectedEventArgs(this.mockChannel.Object)); + }); + this.SetupChannelMessage(MessageType.VersionCheck, MessageType.VersionCheck, this.protocolConfig.Version); + + this.testRequestSender.InitializeCommunication(); + } + + public void SetupChannelMessage(string messageType, string returnMessageType, TPayload returnPayload) + { + this.mockChannel.Setup(mc => mc.Send(It.Is(s => s.Contains(messageType)))) + .Callback(() => this.mockChannel.Raise(c => c.MessageReceived += null, this.mockChannel.Object, new MessageReceivedEventArgs { Data = messageType })); + + this.mockDataSerializer.Setup(ds => ds.SerializePayload(It.Is(s => s.Equals(messageType)), It.IsAny())).Returns(messageType); + this.mockDataSerializer.Setup(ds => ds.SerializePayload(It.Is(s => s.Equals(messageType)), It.IsAny(), It.IsAny())).Returns(messageType); + this.mockDataSerializer.Setup(ds => ds.DeserializeMessage(It.Is(s => s.Equals(messageType)))).Returns(new Message { MessageType = returnMessageType }); + this.mockDataSerializer.Setup(ds => ds.DeserializePayload(It.Is(m => m.MessageType.Equals(messageType)))).Returns(returnPayload); + } + + public void RaiseMessageReceived(string data) + { + this.mockChannel.Raise(c => c.MessageReceived += null, this.mockChannel.Object, + new MessageReceivedEventArgs { Data = data }); + } + + protected ProxyDiscoveryManager GetProxyDiscoveryManager() + { + this.SetupAndInitializeTestRequestSender(); + var testDiscoveryManager = new ProxyDiscoveryManager( + mockRequestData.Object, + testRequestSender, + mockTestHostManager.Object, + mockDataSerializer.Object, + clientConnectionTimeout); + + return testDiscoveryManager; + } + + internal ProxyExecutionManager GetProxyExecutionManager() + { + this.SetupAndInitializeTestRequestSender(); + var testExecutionManager = new ProxyExecutionManager(mockRequestData.Object, testRequestSender, + mockTestHostManager.Object, mockDataSerializer.Object, clientConnectionTimeout); + + return testExecutionManager; + } + } +} diff --git a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/ProxyDiscoveryManagerTests.cs b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/ProxyDiscoveryManagerTests.cs index 7830841e48..2d417e5923 100644 --- a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/ProxyDiscoveryManagerTests.cs +++ b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/ProxyDiscoveryManagerTests.cs @@ -3,51 +3,43 @@ namespace TestPlatform.CrossPlatEngine.UnitTests.Client { - using System; - using System.Collections.Generic; - using System.Linq; - using System.Net; - using System.Threading; - using System.Threading.Tasks; - using Microsoft.VisualStudio.TestPlatform.Common.ExtensionFramework; using Microsoft.VisualStudio.TestPlatform.Common.Telemetry; + using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities; using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.Interfaces; + using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.ObjectModel; using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Client; using Microsoft.VisualStudio.TestPlatform.ObjectModel; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Host; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; using Microsoft.VisualStudio.TestTools.UnitTesting; - using Moq; - + using System; + using System.Collections.Generic; + using System.Linq; + using System.Net; + using System.Threading; + using System.Threading.Tasks; using TestPlatform.Common.UnitTests.ExtensionFramework; - using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities; - using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.ObjectModel; [TestClass] - public class ProxyDiscoveryManagerTests + public class ProxyDiscoveryManagerTests : ProxyBaseManagerTests { + //private const int CLIENTPROCESSEXITWAIT = 10 * 1000; + private readonly DiscoveryCriteria discoveryCriteria; private ProxyDiscoveryManager testDiscoveryManager; - private Mock mockTestHostManager; - private Mock mockRequestSender; - private Mock mockDataSerializer; + //private Mock mockDataSerializer; private Mock mockRequestData; private Mock mockMetricsCollection; - private ITestRequestSender testRequestSender; - private Mock mockCommunicationManager; - - ProtocolConfig protocolConfig = new ProtocolConfig { Version = 2 }; - /// /// The client connection timeout in milliseconds for unit tests. /// @@ -55,16 +47,10 @@ public class ProxyDiscoveryManagerTests public ProxyDiscoveryManagerTests() { - this.mockTestHostManager = new Mock(); this.mockRequestSender = new Mock(); - this.mockDataSerializer = new Mock(); this.mockRequestData = new Mock(); this.mockMetricsCollection = new Mock(); this.mockRequestData.Setup(rd => rd.MetricsCollection).Returns(this.mockMetricsCollection.Object); - - this.mockDataSerializer.Setup(mds => mds.DeserializeMessage(null)).Returns(new Message()); - this.mockDataSerializer.Setup(mds => mds.DeserializeMessage(string.Empty)).Returns(new Message()); - this.testDiscoveryManager = new ProxyDiscoveryManager( this.mockRequestData.Object, this.mockRequestSender.Object, @@ -72,22 +58,6 @@ public ProxyDiscoveryManagerTests() this.mockDataSerializer.Object, this.testableClientConnectionTimeout); this.discoveryCriteria = new DiscoveryCriteria(new[] { "test.dll" }, 1, string.Empty); - - // Default setup test host manager as shared (desktop) - this.mockTestHostManager.SetupGet(th => th.Shared).Returns(true); - this.mockTestHostManager.Setup( - m => m.GetTestHostProcessStartInfo( - It.IsAny>(), - It.IsAny>(), - It.IsAny())) - .Returns(new TestProcessStartInfo()); - this.mockTestHostManager.Setup(tmh => tmh.LaunchTestHostAsync(It.IsAny(), It.IsAny())) - .Callback( - () => - { - this.mockTestHostManager.Raise(thm => thm.HostLaunched += null, new HostProviderEventArgs(string.Empty)); - }) - .Returns(Task.FromResult(true)); } [TestMethod] @@ -316,15 +286,15 @@ public void DiscoverTestsCloseTestHostIfRawMessageIsOfTypeDiscoveryComplete() { Mock mockTestDiscoveryEventsHandler = new Mock(); - this.mockDataSerializer.Setup(mds => mds.DeserializeMessage(It.IsAny())).Returns( () => - { - var message = new Message - { - MessageType = MessageType.DiscoveryComplete - }; + this.mockDataSerializer.Setup(mds => mds.DeserializeMessage(It.IsAny())).Returns(() => + { + var message = new Message + { + MessageType = MessageType.DiscoveryComplete + }; - return message; - }); + return message; + }); // Act. this.testDiscoveryManager.DiscoverTests(this.discoveryCriteria, mockTestDiscoveryEventsHandler.Object); @@ -359,11 +329,10 @@ public void DiscoverTestsShouldNotCloseTestHostIfRawMessageIsNotOfTypeDiscoveryC public void DiscoveryManagerShouldPassOnHandleDiscoveredTests() { Mock mockTestDiscoveryEventsHandler = new Mock(); - this.mockRequestSender.Setup(s => s.WaitForRequestHandlerConnection(It.IsAny())).Returns(true); var testCases = new List() { new TestCase("x.y.z", new Uri("x://y"), "x.dll") }; - var rawMessage = "OnDiscoveredTests"; - var message = new Message() { MessageType = MessageType.TestCasesFound, Payload = null }; - this.SetupReceiveRawMessageAsyncAndDeserializeMessageAndInitialize(rawMessage, message); + + this.testDiscoveryManager = this.GetProxyDiscoveryManager(); + this.SetupChannelMessage(MessageType.StartDiscovery, MessageType.TestCasesFound, testCases); var completePayload = new DiscoveryCompletePayload() { @@ -379,7 +348,6 @@ public void DiscoveryManagerShouldPassOnHandleDiscoveredTests() this.mockDataSerializer.Setup(ds => ds.DeserializePayload(completeMessage)).Returns(completePayload); }); - // Act. this.testDiscoveryManager.DiscoverTests(this.discoveryCriteria, mockTestDiscoveryEventsHandler.Object); @@ -409,40 +377,47 @@ public void DiscoveryManagerShouldPassOnHandleLogMessage() mockTestDiscoveryEventsHandler.Verify(mtdeh => mtdeh.HandleLogMessage(It.IsAny(), It.IsAny()), Times.Once); } - private void SetupReceiveRawMessageAsyncAndDeserializeMessageAndInitialize(string rawMessage, Message message) - { - TestHostConnectionInfo connectionInfo; - connectionInfo = new TestHostConnectionInfo - { - Endpoint = IPAddress.Loopback + ":0", - Role = ConnectionRole.Client, - Transport = Transport.Sockets - }; - this.mockCommunicationManager = new Mock(); - this.mockDataSerializer = new Mock(); - this.testRequestSender = new TestRequestSender(this.mockCommunicationManager.Object, connectionInfo, this.mockDataSerializer.Object, this.protocolConfig); - this.mockCommunicationManager.Setup(mc => mc.HostServer(It.IsAny())).Returns(new IPEndPoint(IPAddress.Loopback, 0)); - this.mockCommunicationManager.Setup(mc => mc.WaitForClientConnection(It.IsAny())).Returns(true); - this.testRequestSender.InitializeCommunication(); - this.mockCommunicationManager.Setup(mc => mc.ReceiveRawMessageAsync(It.IsAny())).Returns(Task.FromResult(rawMessage)); - this.mockDataSerializer.Setup(ds => ds.DeserializeMessage(rawMessage)).Returns(message); - - this.testDiscoveryManager = new ProxyDiscoveryManager( - this.mockRequestData.Object, - this.testRequestSender, - this.mockTestHostManager.Object, - this.mockDataSerializer.Object, - this.testableClientConnectionTimeout); - - this.CheckAndSetProtocolVersion(); - } - - private void CheckAndSetProtocolVersion() - { - var message = new Message() { MessageType = MessageType.VersionCheck, Payload = this.protocolConfig.Version }; - this.mockCommunicationManager.Setup(mc => mc.ReceiveMessage()).Returns(message); - this.mockDataSerializer.Setup(ds => ds.DeserializePayload(It.IsAny())).Returns(this.protocolConfig.Version); - this.testRequestSender.CheckVersionWithTestHost(); - } + //private void SetupAndInitializeTestRequestSender() + //{ + // var connectionInfo = new TestHostConnectionInfo + // { + // Endpoint = IPAddress.Loopback + ":0", + // Role = ConnectionRole.Client, + // Transport = Transport.Sockets + // }; + + // this.mockCommunicationEndpoint = new Mock(); + // this.mockDataSerializer = new Mock(); + // this.testRequestSender = new TestRequestSender(this.mockCommunicationEndpoint.Object, connectionInfo, this.mockDataSerializer.Object, this.protocolConfig, CLIENTPROCESSEXITWAIT); + // this.mockCommunicationEndpoint.Setup(mc => mc.Start(connectionInfo.Endpoint)).Returns(connectionInfo.Endpoint).Callback(() => + // { + // this.mockCommunicationEndpoint.Raise( + // s => s.Connected += null, + // this.mockCommunicationEndpoint.Object, + // new ConnectedEventArgs(this.mockChannel.Object)); + // }); + // this.SetupChannelMessage(MessageType.VersionCheck, MessageType.VersionCheck, this.protocolConfig.Version); + // this.testRequestSender.InitializeCommunication(); + + // this.testDiscoveryManager = new ProxyDiscoveryManager( + // this.mockRequestData.Object, + // this.testRequestSender, + // this.mockTestHostManager.Object, + // this.mockDataSerializer.Object, + // this.testableClientConnectionTimeout); + //} + + //private void SetupChannelMessage(string messageType, string returnMessageType, TPayload returnPayload) + //{ + // this.mockChannel.Setup(mc => mc.Send(It.Is(s => s.Contains(messageType)))) + // .Callback(() => this.mockChannel.Raise(c => c.MessageReceived += null, this.mockChannel.Object, new MessageReceivedEventArgs { Data = messageType })); + + // this.mockDataSerializer.Setup(ds => ds.SerializePayload(It.Is(s => s.Equals(messageType)), It.IsAny())).Returns(messageType); + // this.mockDataSerializer.Setup(ds => ds.SerializePayload(It.Is(s => s.Equals(messageType)), It.IsAny(), It.IsAny())).Returns(messageType); + // this.mockDataSerializer.Setup(ds => ds.DeserializeMessage(It.Is(s => s.Equals(messageType)))) + // .Returns(new Message { MessageType = returnMessageType }); + // this.mockDataSerializer.Setup(ds => ds.DeserializePayload(It.Is(m => m.MessageType.Equals(messageType)))) + // .Returns(returnPayload); + //} } } diff --git a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/ProxyExecutionManagerTests.cs b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/ProxyExecutionManagerTests.cs index 2878f2b44e..ad2f27fe6d 100644 --- a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/ProxyExecutionManagerTests.cs +++ b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/ProxyExecutionManagerTests.cs @@ -25,10 +25,8 @@ namespace TestPlatform.CrossPlatEngine.UnitTests.Client using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities; [TestClass] - public class ProxyExecutionManagerTests + public class ProxyExecutionManagerTests : ProxyBaseManagerTests { - private readonly Mock mockTestHostManager; - private readonly Mock mockRequestSender; private readonly Mock mockTestRunCriteria; @@ -37,15 +35,9 @@ public class ProxyExecutionManagerTests private Mock mockMetricsCollection; - private ITestRequestSender testRequestSender; - - private Mock mockCommunicationManager; - private ProxyExecutionManager testExecutionManager; - private Mock mockDataSerializer; - - ProtocolConfig protocolConfig = new ProtocolConfig { Version = 2 }; + //private Mock mockDataSerializer; /// /// The client connection timeout in milliseconds for unit tests. @@ -53,34 +45,17 @@ public class ProxyExecutionManagerTests private int clientConnectionTimeout = 400; public ProxyExecutionManagerTests() { - this.mockTestHostManager = new Mock(); this.mockRequestSender = new Mock(); this.mockTestRunCriteria = new Mock(new List { "source.dll" }, 10); - this.mockDataSerializer = new Mock(); + //this.mockDataSerializer = new Mock(); this.mockRequestData = new Mock(); this.mockMetricsCollection = new Mock(); this.mockRequestData.Setup(rd => rd.MetricsCollection).Returns(this.mockMetricsCollection.Object); this.testExecutionManager = new ProxyExecutionManager(this.mockRequestData.Object, this.mockRequestSender.Object, this.mockTestHostManager.Object, this.mockDataSerializer.Object, this.clientConnectionTimeout); - this.mockDataSerializer.Setup(mds => mds.DeserializeMessage(null)).Returns(new Message()); - this.mockDataSerializer.Setup(mds => mds.DeserializeMessage(string.Empty)).Returns(new Message()); - - // Default to shared test host - this.mockTestHostManager.SetupGet(th => th.Shared).Returns(true); - this.mockTestHostManager.Setup( - m => m.GetTestHostProcessStartInfo( - It.IsAny>(), - It.IsAny>(), - It.IsAny())) - .Returns(new TestProcessStartInfo()); - this.mockTestHostManager.Setup(tmh => tmh.LaunchTestHostAsync(It.IsAny(), It.IsAny())) - .Callback( - () => - { - this.mockTestHostManager.Raise(thm => thm.HostLaunched += null, new HostProviderEventArgs(string.Empty)); - }) - .Returns(Task.FromResult(true)); + //this.mockDataSerializer.Setup(mds => mds.DeserializeMessage(null)).Returns(new Message()); + //this.mockDataSerializer.Setup(mds => mds.DeserializeMessage(string.Empty)).Returns(new Message()); } [TestMethod] @@ -471,13 +446,9 @@ public void ExecutionManagerShouldPassOnTestRunStatsChange() var runCriteria = new Mock( new List { new TestCase("A.C.M", new System.Uri("executor://dummy"), "source.dll") }, 10); - var testRunChangedArgs = new TestRunChangedEventArgs(null, null, null); - var rawMessage = "OnTestRunStatsChange"; - var message = new Message() { MessageType = MessageType.TestRunStatsChange, Payload = null }; - this.SetupReceiveRawMessageAsyncAndDeserializeMessageAndInitialize(rawMessage, message); - this.mockDataSerializer.Setup(ds => ds.DeserializePayload(message)).Returns(testRunChangedArgs); + this.testExecutionManager = this.GetProxyExecutionManager(); var completePayload = new TestRunCompletePayload() { @@ -487,11 +458,16 @@ public void ExecutionManagerShouldPassOnTestRunStatsChange() TestRunCompleteArgs = null }; var completeMessage = new Message() { MessageType = MessageType.ExecutionComplete, Payload = null }; - mockTestRunEventsHandler.Setup(mh => mh.HandleTestRunStatsChange(testRunChangedArgs)).Callback( + this.SetupChannelMessage(MessageType.StartTestExecutionWithTests, MessageType.TestRunStatsChange, testRunChangedArgs); + + mockTestRunEventsHandler.Setup(mh => mh.HandleTestRunStatsChange(It.IsAny())).Callback( () => { this.mockDataSerializer.Setup(ds => ds.DeserializeMessage(It.IsAny())).Returns(completeMessage); this.mockDataSerializer.Setup(ds => ds.DeserializePayload(completeMessage)).Returns(completePayload); + this.mockDataSerializer.Setup(ds => ds.SerializeMessage(It.IsAny())) + .Returns(MessageType.SessionEnd); + this.RaiseMessageReceived(MessageType.ExecutionComplete); }); var waitHandle = new AutoResetEvent(false); @@ -500,9 +476,11 @@ public void ExecutionManagerShouldPassOnTestRunStatsChange() It.IsAny(), It.IsAny>(), It.IsAny>())).Callback(() => waitHandle.Set()); + // Act. this.testExecutionManager.StartTestRun(runCriteria.Object, mockTestRunEventsHandler.Object); waitHandle.WaitOne(); + // Verify mockTestRunEventsHandler.Verify(mtdeh => mtdeh.HandleTestRunStatsChange(It.IsAny()), Times.Once); } @@ -537,13 +515,9 @@ public void ExecutionManagerShouldPassOnLaunchProcessWithDebuggerAttached() var runCriteria = new Mock( new List { new TestCase("A.C.M", new System.Uri("executor://dummy"), "source.dll") }, 10); - - var rawMessage = "LaunchProcessWithDebugger"; - var message = new Message() { MessageType = MessageType.LaunchAdapterProcessWithDebuggerAttached, Payload = null }; var payload = new TestProcessStartInfo(); - this.SetupReceiveRawMessageAsyncAndDeserializeMessageAndInitialize(rawMessage, message); - this.mockDataSerializer.Setup(ds => ds.DeserializePayload(message)).Returns(payload); + this.testExecutionManager = this.GetProxyExecutionManager(); var completePayload = new TestRunCompletePayload() { @@ -553,11 +527,16 @@ public void ExecutionManagerShouldPassOnLaunchProcessWithDebuggerAttached() TestRunCompleteArgs = null }; var completeMessage = new Message() { MessageType = MessageType.ExecutionComplete, Payload = null }; - mockTestRunEventsHandler.Setup(mh => mh.LaunchProcessWithDebuggerAttached(payload)).Callback( + this.SetupChannelMessage(MessageType.StartTestExecutionWithTests, + MessageType.LaunchAdapterProcessWithDebuggerAttached, payload); + mockTestRunEventsHandler.Setup(mh => mh.LaunchProcessWithDebuggerAttached(It.IsAny())).Callback( () => { this.mockDataSerializer.Setup(ds => ds.DeserializeMessage(It.IsAny())).Returns(completeMessage); this.mockDataSerializer.Setup(ds => ds.DeserializePayload(completeMessage)).Returns(completePayload); + this.mockDataSerializer.Setup(ds => ds.SerializeMessage(It.IsAny())) + .Returns(MessageType.SessionEnd); + this.RaiseMessageReceived(MessageType.ExecutionComplete); }); var waitHandle = new AutoResetEvent(false); @@ -583,35 +562,40 @@ private void SignalEvent(ManualResetEvent manualResetEvent) manualResetEvent.Set(); } - private void SetupReceiveRawMessageAsyncAndDeserializeMessageAndInitialize(string rawMessage, Message message) - { - TestHostConnectionInfo connectionInfo; - connectionInfo = new TestHostConnectionInfo - { - Endpoint = IPAddress.Loopback + ":0", - Role = ConnectionRole.Client, - Transport = Transport.Sockets - }; - this.mockCommunicationManager = new Mock(); - this.mockDataSerializer = new Mock(); - this.testRequestSender = new TestRequestSender(this.mockCommunicationManager.Object, connectionInfo, this.mockDataSerializer.Object, this.protocolConfig); - this.mockCommunicationManager.Setup(mc => mc.HostServer(It.IsAny())).Returns(new IPEndPoint(IPAddress.Loopback, 0)); - this.mockCommunicationManager.Setup(mc => mc.WaitForClientConnection(It.IsAny())).Returns(true); - this.testRequestSender.InitializeCommunication(); - this.mockCommunicationManager.Setup(mc => mc.ReceiveRawMessageAsync(It.IsAny())).Returns(Task.FromResult(rawMessage)); - this.mockDataSerializer.Setup(ds => ds.DeserializeMessage(rawMessage)).Returns(message); - - this.testExecutionManager = new ProxyExecutionManager(this.mockRequestData.Object, this.testRequestSender, this.mockTestHostManager.Object, this.mockDataSerializer.Object, this.clientConnectionTimeout); - - this.CheckAndSetProtocolVersion(); - } - - private void CheckAndSetProtocolVersion() - { - var message = new Message() { MessageType = MessageType.VersionCheck, Payload = this.protocolConfig.Version }; - this.mockCommunicationManager.Setup(mc => mc.ReceiveMessage()).Returns(message); - this.mockDataSerializer.Setup(ds => ds.DeserializePayload(It.IsAny())).Returns(this.protocolConfig.Version); - this.testRequestSender.CheckVersionWithTestHost(); - } + //private void SetupReceiveRawMessageAsyncAndDeserializeMessageAndInitialize() + //{ + // var connectionInfo = new TestHostConnectionInfo + // { + // Endpoint = IPAddress.Loopback + ":0", + // Role = ConnectionRole.Client, + // Transport = Transport.Sockets + // }; + // this.mockCommunicationEndpoint = new Mock(); + // this.mockDataSerializer = new Mock(); + // this.testRequestSender = new TestRequestSender(this.mockCommunicationEndpoint.Object, connectionInfo, this.mockDataSerializer.Object, this.protocolConfig, CLIENTPROCESSEXITWAIT); + // this.mockCommunicationEndpoint.Setup(mc => mc.Start(connectionInfo.Endpoint)).Returns(connectionInfo.Endpoint).Callback(() => + // { + // this.mockCommunicationEndpoint.Raise( + // s => s.Connected += null, + // this.mockCommunicationEndpoint.Object, + // new ConnectedEventArgs(this.mockChannel.Object)); + // }); + // this.SetupChannelMessage(MessageType.VersionCheck, MessageType.VersionCheck, this.protocolConfig.Version); + + // this.testRequestSender.InitializeCommunication(); + + // this.testExecutionManager = new ProxyExecutionManager(this.mockRequestData.Object, this.testRequestSender, this.mockTestHostManager.Object, this.mockDataSerializer.Object, this.clientConnectionTimeout); + //} + + //private void SetupChannelMessage(string messageType, string returnMessageType, TPayload returnPayload) + //{ + // this.mockChannel.Setup(mc => mc.Send(It.Is(s => s.Contains(messageType)))) + // .Callback(() => this.mockChannel.Raise(c => c.MessageReceived += null, this.mockChannel.Object, new MessageReceivedEventArgs { Data = messageType })); + + // this.mockDataSerializer.Setup(ds => ds.SerializePayload(It.Is(s => s.Equals(messageType)), It.IsAny())).Returns(messageType); + // this.mockDataSerializer.Setup(ds => ds.SerializePayload(It.Is(s => s.Equals(messageType)), It.IsAny(), It.IsAny())).Returns(messageType); + // this.mockDataSerializer.Setup(ds => ds.DeserializeMessage(It.Is(s => s.Equals(messageType)))).Returns(new Message { MessageType = returnMessageType }); + // this.mockDataSerializer.Setup(ds => ds.DeserializePayload(It.Is(m => m.MessageType.Equals(messageType)))).Returns(returnPayload); + //} } } diff --git a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/ProxyOperationManagerTests.cs b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/ProxyOperationManagerTests.cs index d10e302439..6e047c7b83 100644 --- a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/ProxyOperationManagerTests.cs +++ b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/ProxyOperationManagerTests.cs @@ -27,11 +27,11 @@ namespace TestPlatform.CrossPlatEngine.UnitTests.Client using Moq; [TestClass] - public class ProxyOperationManagerTests + public class ProxyOperationManagerTests : ProxyBaseManagerTests { - private readonly ProxyOperationManager testOperationManager; + private const int CLIENTPROCESSEXITWAIT = 10 * 1000; - private readonly Mock mockTestHostManager; + private readonly ProxyOperationManager testOperationManager; private readonly Mock mockRequestSender; @@ -50,23 +50,11 @@ public class ProxyOperationManagerTests public ProxyOperationManagerTests() { - this.mockTestHostManager = new Mock(); this.mockRequestSender = new Mock(); this.mockRequestSender.Setup(rs => rs.WaitForRequestHandlerConnection(this.connectionTimeout)).Returns(true); this.mockRequestData = new Mock(); this.mockRequestData.Setup(rd => rd.MetricsCollection).Returns(new Mock().Object); this.testOperationManager = new TestableProxyOperationManager(this.mockRequestData.Object, this.mockRequestSender.Object, this.mockTestHostManager.Object, this.connectionTimeout); - - this.mockTestHostManager.Setup( - m => m.GetTestHostProcessStartInfo( - It.IsAny>(), - It.IsAny>(), - It.IsAny())) - .Returns(new TestProcessStartInfo()); - - this.mockTestHostManager.Setup(tmh => tmh.LaunchTestHostAsync(It.IsAny(), It.IsAny())) - .Callback(() => this.mockTestHostManager.Raise(t => t.HostLaunched += null, new HostProviderEventArgs(string.Empty))) - .Returns(Task.FromResult(true)); } [TestMethod] @@ -135,19 +123,17 @@ public void SetupChannelShouldCallHostServerIfRunnerIsServer() var connectionInfo = new TestHostConnectionInfo { Endpoint = IPAddress.Loopback + ":0", - Role = ConnectionRole.Client, + Role = ConnectionRole.Host, Transport = Transport.Sockets }; ProtocolConfig protocolConfig = new ProtocolConfig { Version = 2 }; - var mockCommunicationManager = new Mock(); - mockCommunicationManager.Setup(mc => mc.HostServer(new IPEndPoint(IPAddress.Loopback, 0))).Returns(new IPEndPoint(IPAddress.Loopback, 123)); - mockCommunicationManager.Setup(mc => mc.WaitForClientConnection(It.IsAny())).Returns(true); - var mockDataSerializer = new Mock(); - var testRequestSender = new TestRequestSender(mockCommunicationManager.Object, connectionInfo, mockDataSerializer.Object, protocolConfig); + var mockCommunicationServer = new Mock(); + + mockCommunicationServer.Setup(mc => mc.Start(connectionInfo.Endpoint)).Returns(IPAddress.Loopback + ":123").Callback( + () => { mockCommunicationServer.Raise(s=>s.Connected += null, mockCommunicationServer.Object, new ConnectedEventArgs(this.mockChannel.Object)); }); - var message = new Message() { MessageType = MessageType.VersionCheck, Payload = protocolConfig.Version }; - mockCommunicationManager.Setup(mc => mc.ReceiveMessage()).Returns(message); - mockDataSerializer.Setup(ds => ds.DeserializePayload(It.IsAny())).Returns(protocolConfig.Version); + var testRequestSender = new TestRequestSender(mockCommunicationServer.Object, connectionInfo, mockDataSerializer.Object, protocolConfig, CLIENTPROCESSEXITWAIT); + this.SetupChannelMessage(MessageType.VersionCheck, MessageType.VersionCheck, protocolConfig.Version); this.mockTestHostManager.Setup(thm => thm.GetTestHostConnectionInfo()).Returns(connectionInfo); @@ -155,8 +141,7 @@ public void SetupChannelShouldCallHostServerIfRunnerIsServer() localTestOperationManager.SetupChannel(Enumerable.Empty(), CancellationToken.None); - mockCommunicationManager.Verify(s => s.HostServer(new IPEndPoint(IPAddress.Loopback, 0)), Times.Once); - mockCommunicationManager.Verify(s => s.AcceptClientAsync(), Times.Once); + mockCommunicationServer.Verify(s => s.Start(IPAddress.Loopback.ToString()+":0"), Times.Once); } [TestMethod] @@ -164,27 +149,30 @@ public void SetupChannelShouldCallSetupClientIfRunnerIsClient() { var connectionInfo = new TestHostConnectionInfo { - Endpoint = IPAddress.Loopback + ":0", + Endpoint = IPAddress.Loopback + ":124", Role = ConnectionRole.Host, Transport = Transport.Sockets }; ProtocolConfig protocolConfig = new ProtocolConfig { Version = 2 }; - var mockCommunicationManager = new Mock(); - mockCommunicationManager.Setup(mc => mc.SetupClientAsync(new IPEndPoint(IPAddress.Loopback, 0))); - mockCommunicationManager.Setup(mc => mc.WaitForServerConnection(It.IsAny())).Returns(true); - var mockDataSerializer = new Mock(); - var testRequestSender = new TestRequestSender(mockCommunicationManager.Object, connectionInfo, mockDataSerializer.Object, new ProtocolConfig { Version = 2 }); + var mockCommunicationEndpoint = new Mock(); + mockCommunicationEndpoint.Setup(mc => mc.Start(connectionInfo.Endpoint)).Returns(connectionInfo.Endpoint).Callback(() => + { + mockCommunicationEndpoint.Raise( + s => s.Connected += null, + mockCommunicationEndpoint.Object, + new ConnectedEventArgs(this.mockChannel.Object)); + }); + + this.SetupChannelMessage(MessageType.VersionCheck, MessageType.VersionCheck, protocolConfig.Version); + var testRequestSender = new TestRequestSender(mockCommunicationEndpoint.Object, connectionInfo, mockDataSerializer.Object, new ProtocolConfig { Version = 2 }, CLIENTPROCESSEXITWAIT); this.mockTestHostManager.Setup(thm => thm.GetTestHostConnectionInfo()).Returns(connectionInfo); - var message = new Message() { MessageType = MessageType.VersionCheck, Payload = protocolConfig.Version }; - mockCommunicationManager.Setup(mc => mc.ReceiveMessage()).Returns(message); - mockDataSerializer.Setup(ds => ds.DeserializePayload(It.IsAny())).Returns(protocolConfig.Version); var localTestOperationManager = new TestableProxyOperationManager(this.mockRequestData.Object, testRequestSender, this.mockTestHostManager.Object, this.connectionTimeout); localTestOperationManager.SetupChannel(Enumerable.Empty(), CancellationToken.None); - mockCommunicationManager.Verify(s => s.SetupClientAsync(It.IsAny()), Times.Once); + mockCommunicationEndpoint.Verify(s => s.Start(It.IsAny()), Times.Once); } [TestMethod] @@ -449,6 +437,19 @@ private void SetUpMocksForDotNetTestHost() }).Returns(Process.GetCurrentProcess()); } + //private void SetupChannelMessage(string messageType, string returnMessageType, TPayload returnPayload) + //{ + // this.mockChannel.Setup(mc => mc.Send(It.Is(s => s.Contains(messageType)))) + // .Callback(() => this.mockChannel.Raise(c => c.MessageReceived += null, this.mockChannel.Object, new MessageReceivedEventArgs { Data = messageType })); + + // this.mockDataSerializer.Setup(ds => ds.SerializePayload(It.Is(s => s.Equals(messageType)), It.IsAny())).Returns(messageType); + // this.mockDataSerializer.Setup(ds => ds.SerializePayload(It.Is(s => s.Equals(messageType)), It.IsAny(), It.IsAny())).Returns(messageType); + // this.mockDataSerializer.Setup(ds => ds.DeserializeMessage(It.Is(s => s.Equals(messageType)))) + // .Returns(new Message { MessageType = returnMessageType }); + // this.mockDataSerializer.Setup(ds => ds.DeserializePayload(It.Is(m => m.MessageType.Equals(messageType)))) + // .Returns(returnPayload); + //} + private class TestableProxyOperationManager : ProxyOperationManager { public TestableProxyOperationManager( diff --git a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/EventHandlers/TestRequestHandlerTests.cs b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/EventHandlers/TestRequestHandlerTests.cs index cfd8a5fc3d..8a0061b411 100644 --- a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/EventHandlers/TestRequestHandlerTests.cs +++ b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/EventHandlers/TestRequestHandlerTests.cs @@ -1,120 +1,550 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -namespace TestPlatform.CrossPlatEngine.UnitTests +using System; +using System.Net.Sockets; +using Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine; +using Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine.ClientProtocol; +using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; +using Microsoft.VisualStudio.TestPlatform.Utilities; + +namespace Microsoft.VisualStudio.TestPlatform.CommunicationUtilities { - using System.Net; + using System.IO; - using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities; using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.Interfaces; - using Microsoft.VisualStudio.TestPlatform.ObjectModel; - using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; - using Microsoft.VisualStudio.TestTools.UnitTesting; + using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine.TesthostProtocol; + using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.ObjectModel; + using System.Threading.Tasks; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using System.Collections.Generic; + using System.Linq; + using System.Net; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; [TestClass] public class TestRequestHandlerTests { - private ITestRequestHandler testRequestHandler; - private Mock mockCommunicationManager; - private Mock mockDataSerializer; - private ProtocolConfig protocolConfig = new ProtocolConfig { Version = 2 }; - private TestHostConnectionInfo connectionInfo; + private readonly Mock mockCommunicationClient; + private readonly Mock mockChannel; + private readonly Mock mockTestHostManagerFactory; + private readonly Mock mockDiscoveryManager; + private readonly Mock mockExecutionManager; + + private readonly JsonDataSerializer dataSerializer; + private readonly ITestRequestHandler requestHandler; + private readonly TestHostConnectionInfo testHostConnectionInfo; + private readonly JobQueue jobQueue; + + public TestRequestHandlerTests() + { + this.mockCommunicationClient = new Mock(); + this.mockChannel = new Mock(); + this.dataSerializer = JsonDataSerializer.Instance; + this.testHostConnectionInfo = new TestHostConnectionInfo + { + Endpoint = IPAddress.Loopback + ":123", + Role = ConnectionRole.Host + }; + + this.jobQueue = new JobQueue( + action => { action(); }, + "TestHostOperationQueue", + 500, + 25000000, + true, + message => EqtTrace.Error(message)); + + // Setup mock discovery and execution managers + this.mockTestHostManagerFactory = new Mock(); + this.mockDiscoveryManager = new Mock(); + this.mockExecutionManager = new Mock(); + this.mockTestHostManagerFactory.Setup(mf => mf.GetDiscoveryManager()).Returns(this.mockDiscoveryManager.Object); + this.mockTestHostManagerFactory.Setup(mf => mf.GetExecutionManager()).Returns(this.mockExecutionManager.Object); + + this.requestHandler = new TestableTestRequestHandler( + this.testHostConnectionInfo, + this.mockCommunicationClient.Object, + JsonDataSerializer.Instance, + jobQueue); + } + + [TestMethod] + public void InitializeCommunicationShouldConnectToServerAsynchronously() + { + this.requestHandler.InitializeCommunication(); + + this.mockCommunicationClient.Verify(c => c.Start(this.testHostConnectionInfo.Endpoint), Times.Once); + } + + [TestMethod] + public void InitializeCommunicationShouldThrowIfServerIsNotAccessible() + { + var connectionInfo = new TestHostConnectionInfo + { + Endpoint = IPAddress.Loopback + ":123", + Role = ConnectionRole.Host + }; + var socketClient = new SocketClient(); + socketClient.Connected += (sender, connectedEventArgs) => + { + Assert.IsFalse(connectedEventArgs.Connected); + Assert.AreEqual(typeof(SocketException), connectedEventArgs.Fault.InnerException.GetType()); + }; + var rh = new TestableTestRequestHandler(connectionInfo, socketClient, this.dataSerializer, this.jobQueue); + + rh.InitializeCommunication(); + this.requestHandler.WaitForRequestSenderConnection(1000); + } + + [TestMethod] + public void WaitForRequestSenderConnectionShouldWaitUntilConnectionIsSetup() + { + this.SetupChannel(); + + Assert.IsTrue(this.requestHandler.WaitForRequestSenderConnection(1000)); + } + + [TestMethod] + public void WaitForRequestSenderConnectionShouldReturnFalseIfConnectionSetupTimesout() + { + this.requestHandler.InitializeCommunication(); + + Assert.IsFalse(this.requestHandler.WaitForRequestSenderConnection(1)); + } + + [TestMethod] + public void ProcessRequestsShouldProcessMessagesUntilSessionCompleted() + { + this.SetupChannel(); + + var task = this.ProcessRequestsAsync(this.mockTestHostManagerFactory.Object); + + this.SendSessionEnd(); + Assert.IsTrue(task.Wait(5)); + } + + #region Version Check Protocol - [TestInitialize] - public void TestInit() + [TestMethod] + public void ProcessRequestsVersionCheckShouldAckMinimumOfGivenAndHighestSupportedVersion() { - this.connectionInfo = new TestHostConnectionInfo + var message = new Message { MessageType = MessageType.VersionCheck, Payload = 1 }; + this.SetupChannel(); + this.ProcessRequestsAsync(this.mockTestHostManagerFactory.Object); + + this.SendMessageOnChannel(message); + + this.VerifyResponseMessageEquals(this.Serialize(message)); + this.SendSessionEnd(); + } + + [TestMethod] + public void ProcessRequestsVersionCheckShouldLogErrorIfDiagnosticsEnableFails() + { + if (!string.IsNullOrEmpty(EqtTrace.LogFile)) { - Endpoint = IPAddress.Loopback + ":0", - Role = ConnectionRole.Client, - Transport = Transport.Sockets - }; - this.mockCommunicationManager = new Mock(); - this.mockDataSerializer = new Mock(); - this.testRequestHandler = new TestRequestHandler(this.mockCommunicationManager.Object, this.connectionInfo, this.mockDataSerializer.Object); + // This test is no-op if diagnostics session is enabled + return; + } + EqtTrace.ErrorOnInitialization = "non-existent-error"; + var message = new Message { MessageType = MessageType.VersionCheck, Payload = 1 }; + this.SetupChannel(); + this.ProcessRequestsAsync(this.mockTestHostManagerFactory.Object); + + this.SendMessageOnChannel(message); + + this.VerifyResponseMessageContains(EqtTrace.ErrorOnInitialization); + this.SendSessionEnd(); + } + + #endregion + + #region Discovery Protocol + + [TestMethod] + public void ProcessRequestsDiscoveryInitializeShouldSetExtensionPaths() + { + var message = this.dataSerializer.SerializePayload(MessageType.DiscoveryInitialize, new[] { "testadapter.dll" }); + this.SetupChannel(); + this.ProcessRequestsAsync(this.mockTestHostManagerFactory.Object); + + this.SendMessageOnChannel(message); + this.jobQueue.Flush(); + + this.mockDiscoveryManager.Verify(d => d.Initialize(It.Is>(paths => paths.Any(p => p.Equals("testadapter.dll"))))); + this.SendSessionEnd(); + } + + [TestMethod] + public void ProcessRequestsDiscoveryStartShouldStartDiscoveryWithGivenCriteria() + { + var message = this.dataSerializer.SerializePayload(MessageType.StartDiscovery, new DiscoveryCriteria(new[] { "test.dll" }, 1, string.Empty)); + this.SetupChannel(); + this.ProcessRequestsAsync(this.mockTestHostManagerFactory.Object); + + this.SendMessageOnChannel(message); + this.jobQueue.Flush(); + + this.mockDiscoveryManager.Verify(d => d.DiscoverTests(It.Is(dc => dc.Sources.Contains("test.dll")), It.IsAny())); + this.SendSessionEnd(); } [TestMethod] - public void InitializeCommunicationShouldActAsClientIfTestHostIsClient() + public void DiscoveryCompleteShouldSendDiscoveryCompletePayloadOnChannel() { - this.mockCommunicationManager.Setup(mc => mc.SetupClientAsync(new IPEndPoint(IPAddress.Loopback, 0))); + var discoveryComplete = new DiscoveryCompletePayload { TotalTests = 1, LastDiscoveredTests = Enumerable.Empty(), IsAborted = false }; + var message = this.dataSerializer.SerializePayload(MessageType.DiscoveryComplete, discoveryComplete); + this.SetupChannel(); - this.testRequestHandler.InitializeCommunication(); + this.ProcessRequestsAsync(this.mockTestHostManagerFactory.Object); - this.mockCommunicationManager.Verify(mc => mc.SetupClientAsync(new IPEndPoint(IPAddress.Loopback, 0)), Times.Once); + this.requestHandler.DiscoveryComplete(new DiscoveryCompleteEventArgs(discoveryComplete.TotalTests, discoveryComplete.IsAborted), discoveryComplete.LastDiscoveredTests); + + this.VerifyResponseMessageEquals(message); + this.SendSessionEnd(); } + #endregion + + #region Execution Protocol + [TestMethod] - public void InitializeCommunicationShouldHostServerAndAcceptClientIfTestHostIsServer() + public void ProcessRequestsExecutionInitializeShouldSetExtensionPaths() { - this.mockCommunicationManager.Setup(mc => mc.HostServer(new IPEndPoint(IPAddress.Loopback, 0))).Returns(new IPEndPoint(IPAddress.Loopback, 123)); + var message = this.dataSerializer.SerializePayload(MessageType.ExecutionInitialize, new[] { "testadapter.dll" }); + this.SetupChannel(); - // These settings are that received by(testhost) - this.connectionInfo = new TestHostConnectionInfo - { - Endpoint = IPAddress.Loopback + ":0", - Role = ConnectionRole.Host, - Transport = Transport.Sockets - }; - this.testRequestHandler = new TestRequestHandler(this.mockCommunicationManager.Object, this.connectionInfo, this.mockDataSerializer.Object); + this.ProcessRequestsAsync(this.mockTestHostManagerFactory.Object); - this.testRequestHandler.InitializeCommunication(); + this.SendMessageOnChannel(message); + this.jobQueue.Flush(); - this.mockCommunicationManager.Verify(mc => mc.HostServer(new IPEndPoint(IPAddress.Loopback, 0)), Times.Once); - this.mockCommunicationManager.Verify(mc => mc.AcceptClientAsync(), Times.Once); + this.mockExecutionManager.Verify(e => e.Initialize(It.Is>(paths => paths.Any(p => p.Equals("testadapter.dll"))))); + this.SendSessionEnd(); } [TestMethod] - public void WaitForRequestSenderConnectionShouldCallWaitForServerConnection() + public void ProcessRequestsExecutionStartShouldStartExecutionWithGivenSources() { - this.testRequestHandler.WaitForRequestSenderConnection(123); + var asm = new Dictionary>(); + asm["mstestv2"] = new[] {"test1.dll", "test2.dll"}; + var testRunCriteriaWithSources = new TestRunCriteriaWithSources(asm, "runsettings", null, null); + var message = this.dataSerializer.SerializePayload(MessageType.StartTestExecutionWithSources, testRunCriteriaWithSources); + this.SetupChannel(); - this.mockCommunicationManager.Verify(mc => mc.WaitForServerConnection(123), Times.Once); + this.ProcessRequestsAsync(this.mockTestHostManagerFactory.Object); + + this.SendMessageOnChannel(message); + this.jobQueue.Flush(); + + mockExecutionManager.Verify(e => + e.StartTestRun( + It.Is>>(d => d.ContainsKey("mstestv2")), It.IsAny(), + It.IsAny(), + It.IsAny(), It.IsAny(), + It.IsAny())); + this.SendSessionEnd(); } [TestMethod] - public void WaitForRequestHandlerConnectionShouldCallWaitForClientConnectionIfTestRunnerIsServer() + public void ProcessRequestsExecutionStartShouldStartExecutionWithGivenTests() { - // These settings are that recieved by Test runtime(testhost) - this.connectionInfo = new TestHostConnectionInfo - { - Endpoint = IPAddress.Loopback + ":0", - Role = ConnectionRole.Host, - Transport = Transport.Sockets - }; + var t1 = new TestCase("N.C.M1", new Uri("executor://mstest/v2"), "test1.dll"); + var t2 = new TestCase("N.C.M2", new Uri("executor://mstest/v2"), "test1.dll"); + var testCases = new [] { t1, t2 }; + var testRunCriteriaWithTests = new TestRunCriteriaWithTests(testCases, "runsettings", null, null); + var message = this.dataSerializer.SerializePayload(MessageType.StartTestExecutionWithTests, testRunCriteriaWithTests); + this.SetupChannel(); + + this.ProcessRequestsAsync(this.mockTestHostManagerFactory.Object); + + this.SendMessageOnChannel(message); + this.jobQueue.Flush(); + + mockExecutionManager.Verify(e => + e.StartTestRun( + It.Is>(tcs => + tcs.Any(t => t.FullyQualifiedName.Equals("N.C.M1")) && + tcs.Any(t => t.FullyQualifiedName.Equals("N.C.M2"))), It.IsAny(), + It.IsAny(), + It.IsAny(), It.IsAny(), + It.IsAny())); + this.SendSessionEnd(); + } - this.testRequestHandler = new TestRequestHandler(this.mockCommunicationManager.Object, this.connectionInfo, this.mockDataSerializer.Object); + [TestMethod] + public void ProcessRequestsExecutionCancelShouldCancelTestRun() + { + var message = this.dataSerializer.SerializePayload(MessageType.CancelTestRun, string.Empty); + this.SetupChannel(); - this.testRequestHandler.WaitForRequestSenderConnection(123); + this.ProcessRequestsAsync(this.mockTestHostManagerFactory.Object); + this.SendMessageOnChannel(message); - this.mockCommunicationManager.Verify(mc => mc.WaitForClientConnection(123), Times.Once); + mockExecutionManager.Verify(e => e.Cancel()); + this.SendSessionEnd(); } + // ProcessRequestsExecutionCancelShouldStopRequestProcessing + [TestMethod] - public void CloseShouldCallStopClientOnCommunicationManager() + public void ProcessRequestsExecutionLaunchAdapterProcessWithDebuggerShouldSendAckMessage() { - this.testRequestHandler.Close(); + var message = this.dataSerializer.SerializePayload(MessageType.LaunchAdapterProcessWithDebuggerAttachedCallback, string.Empty); + this.SetupChannel(); + + this.ProcessRequestsAsync(this.mockTestHostManagerFactory.Object); + this.SendMessageOnChannel(message); + this.jobQueue.Flush(); - this.mockCommunicationManager.Verify(mc => mc.StopClient(), Times.Once); + this.SendSessionEnd(); } [TestMethod] - public void CloseShouldCallStopServerOnCommunicationManagerIfTestRunnerIsServer() + public void ProcessRequestsExecutionAbortShouldStopTestRun() { - // These settings are that recieved by Test runtime(testhost) - this.connectionInfo = new TestHostConnectionInfo - { - Endpoint = IPAddress.Loopback + ":0", - Role = ConnectionRole.Host, - Transport = Transport.Sockets - }; + var message = this.dataSerializer.SerializePayload(MessageType.AbortTestRun, string.Empty); + this.SetupChannel(); - this.testRequestHandler = new TestRequestHandler(this.mockCommunicationManager.Object, this.connectionInfo, this.mockDataSerializer.Object); - this.testRequestHandler.Close(); + this.ProcessRequestsAsync(this.mockTestHostManagerFactory.Object); + this.SendMessageOnChannel(message); - this.mockCommunicationManager.Verify(mc => mc.StopServer(), Times.Once); + mockExecutionManager.Verify(e => e.Abort()); + this.SendSessionEnd(); + } + + // ProcessRequestsExecutionAbortShouldStopRequestProcessing + + [TestMethod] + public void SendExecutionCompleteShouldSendTestRunCompletePayloadOnChannel() + { + var t1 = new TestCase("N.C.M1", new Uri("executor://mstest/v2"), "test1.dll"); + var t2 = new TestCase("N.C.M2", new Uri("executor://mstest/v2"), "test1.dll"); + var testCases = new[] { t1, t2 }; + var testRunCriteriaWithTests = new TestRunCriteriaWithTests(testCases, "runsettings", null, null); + var message = this.dataSerializer.SerializePayload(MessageType.StartTestExecutionWithTests, testRunCriteriaWithTests); + this.mockExecutionManager.Setup(em => em.StartTestRun(It.IsAny>(), It.IsAny(), + It.IsAny(), + It.IsAny(), It.IsAny(), + It.IsAny())).Callback(() => + { + this.requestHandler.SendExecutionComplete(It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny>()); + }); + this.SetupChannel(); + + this.ProcessRequestsAsync(this.mockTestHostManagerFactory.Object); + this.mockChannel.Setup(mc => mc.Send(It.Is(d => d.Contains(MessageType.ExecutionComplete)))) + .Callback( + (d) => + { + var msg = this.dataSerializer.DeserializeMessage(d); + var payload = this.dataSerializer.DeserializePayload(msg); + Assert.IsNotNull(payload); + }); + this.SendMessageOnChannel(message); + this.jobQueue.Flush(); + + mockExecutionManager.Verify(e => + e.StartTestRun( + It.Is>(tcs => + tcs.Any(t => t.FullyQualifiedName.Equals("N.C.M1")) && + tcs.Any(t => t.FullyQualifiedName.Equals("N.C.M2"))), It.IsAny(), + It.IsAny(), + It.IsAny(), It.IsAny(), + It.IsAny())); + this.SendSessionEnd(); + } + + [TestMethod] + public void LaunchProcessWithDebuggerAttachedShouldSendProcessInformationOnChannel() + { + var message = this.dataSerializer.SerializePayload(MessageType.LaunchAdapterProcessWithDebuggerAttachedCallback, "123"); + this.SetupChannel(); + + this.mockChannel.Setup(mc => mc.Send(It.Is(d => d.Contains(MessageType.LaunchAdapterProcessWithDebuggerAttached)))) + .Callback( + (d) => + { + var msg = this.dataSerializer.DeserializeMessage(d); + var payload = this.dataSerializer.DeserializePayload(msg); + Assert.IsNotNull(payload); + this.SendMessageOnChannel(message); + this.SendSessionEnd(); + }); + + var task = Task.Run(() => this.requestHandler.LaunchProcessWithDebuggerAttached(new TestProcessStartInfo())); + } + + [TestMethod] + public void LaunchProcessWithDebuggerAttachedShouldWaitForProcessIdFromRunner() + { + var message = dataSerializer.SerializePayload(MessageType.LaunchAdapterProcessWithDebuggerAttachedCallback, "123"); + this.SetupChannel(); + + this.mockChannel.Setup(mc => mc.Send(It.Is(d => d.Contains(MessageType.LaunchAdapterProcessWithDebuggerAttached)))) + .Callback( + (d) => + { + var msg = this.dataSerializer.DeserializeMessage(d); + var payload = this.dataSerializer.DeserializePayload(msg); + Assert.IsNotNull(payload); + this.SendMessageOnChannel(message); + this.SendSessionEnd(); + }); + + var task = Task.Run(() => this.requestHandler.LaunchProcessWithDebuggerAttached(new TestProcessStartInfo())); + + Assert.AreEqual(123, task.Result); + } + #endregion + + #region Logging Protocol + [TestMethod] + public void SendLogShouldSendTestMessageWithLevelOnChannel() + { + var logMsg = "Testing log message on channel"; + this.SetupChannel(); + + this.mockChannel.Setup(mc => mc.Send(It.Is(d => d.Contains(MessageType.TestMessage)))) + .Callback( + (d) => + { + var msg = this.dataSerializer.DeserializeMessage(d); + var payload = this.dataSerializer.DeserializePayload(msg); + Assert.IsNotNull(payload); + Assert.AreEqual(payload.Message, logMsg); + }); + + this.requestHandler.SendLog(TestMessageLevel.Informational, "Testing log message on channel"); + + this.SendSessionEnd(); + } + #endregion + + [TestMethod] + public void ProcessRequestsEndSessionShouldCloseRequestHandler() + { + this.SetupChannel(); + + this.ProcessRequestsAsync(this.mockTestHostManagerFactory.Object); + this.SendSessionEnd(); + + this.mockCommunicationClient.Verify(mc=>mc.Stop(), Times.Once); + } + + [TestMethod] + public void ProcessRequestsAbortSessionShouldBeNoOp() + { + var message = dataSerializer.SerializePayload(MessageType.SessionAbort, string.Empty); + this.SetupChannel(); + + this.ProcessRequestsAsync(this.mockTestHostManagerFactory.Object); + this.SendMessageOnChannel(message); + + // Session abort should not close the client + this.mockCommunicationClient.Verify(mc => mc.Stop(), Times.Never); + } + + [TestMethod] + public void ProcessRequestsInvalidMessageTypeShouldNotThrow() + { + var message = dataSerializer.SerializePayload("DummyMessage", string.Empty); + this.SetupChannel(); + + this.ProcessRequestsAsync(this.mockTestHostManagerFactory.Object); + this.SendMessageOnChannel(message); + + this.SendSessionEnd(); + } + + [TestMethod] + public void ProcessRequestsInvalidMessageTypeShouldProcessFutureMessages() + { + var message = dataSerializer.SerializePayload("DummyMessage", string.Empty); + this.SetupChannel(); + + this.ProcessRequestsAsync(this.mockTestHostManagerFactory.Object); + this.SendMessageOnChannel(message); + + // Should process this message, after the invalid message + this.SendSessionEnd(); + this.mockCommunicationClient.Verify(mc => mc.Stop(), Times.Once); + } + + [TestMethod] + public void DisposeShouldStopCommunicationChannel() + { + var testRequestHandler = this.requestHandler as TestRequestHandler; + Assert.IsNotNull(testRequestHandler); + + testRequestHandler.Dispose(); + + this.mockCommunicationClient.Verify(mc => mc.Stop(), Times.Once); + } + + private void SetupChannel() + { + this.requestHandler.InitializeCommunication(); + this.mockCommunicationClient.Raise(e => e.Connected += null, new ConnectedEventArgs(this.mockChannel.Object)); + } + + private void SendMessageOnChannel(Message message) + { + // Setup message to be returned on deserialization of data + var data = this.Serialize(message); + this.SendMessageOnChannel(data); + } + + private void SendMessageOnChannel(string data) + { + this.mockChannel.Raise(c => c.MessageReceived += null, new MessageReceivedEventArgs { Data = data }); + } + + private void SendSessionEnd() + { + this.SendMessageOnChannel(new Message { MessageType = MessageType.SessionEnd, Payload = string.Empty }); + } + + private Task ProcessRequestsAsync() + { + return Task.Run(() => this.requestHandler.ProcessRequests(new Mock().Object)); + } + + private Task ProcessRequestsAsync(ITestHostManagerFactory testHostManagerFactory) + { + return Task.Run(() => this.requestHandler.ProcessRequests(testHostManagerFactory)); + } + + private string Serialize(Message message) + { + return this.dataSerializer.SerializePayload(message.MessageType, message.Payload); + } + + private void VerifyResponseMessageEquals(string message) + { + this.mockChannel.Verify(mc => mc.Send(It.Is(s => s.Equals(message)))); + } + + private void VerifyResponseMessageContains(string message) + { + this.mockChannel.Verify(mc => mc.Send(It.Is(s => s.Contains(message)))); + } + } + + public class TestableTestRequestHandler : TestRequestHandler + { + public TestableTestRequestHandler(TestHostConnectionInfo testHostConnectionInfo,ICommunicationEndPoint communicationClient, IDataSerializer dataSerializer, JobQueue jobQueue) + : base(testHostConnectionInfo, communicationClient, dataSerializer, jobQueue, OnAckMessageReceived) + { + } + + private static void OnAckMessageReceived(Message message) + { + Assert.AreEqual(message.MessageType, MessageType.LaunchAdapterProcessWithDebuggerAttachedCallback); } } } diff --git a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Microsoft.TestPlatform.CrossPlatEngine.UnitTests.csproj b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Microsoft.TestPlatform.CrossPlatEngine.UnitTests.csproj index 9a6cde39ff..4c2758f99f 100644 --- a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Microsoft.TestPlatform.CrossPlatEngine.UnitTests.csproj +++ b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Microsoft.TestPlatform.CrossPlatEngine.UnitTests.csproj @@ -8,13 +8,13 @@ Exe Microsoft.TestPlatform.CrossPlatEngine.UnitTests - netcoreapp1.0;net451 + net451;netcoreapp1.0 - + true diff --git a/test/Microsoft.TestPlatform.PerformanceTests/SocketTests.cs b/test/Microsoft.TestPlatform.PerformanceTests/SocketTests.cs index 51f3476bf6..5281da2692 100644 --- a/test/Microsoft.TestPlatform.PerformanceTests/SocketTests.cs +++ b/test/Microsoft.TestPlatform.PerformanceTests/SocketTests.cs @@ -31,7 +31,7 @@ public void SocketThroughput2() var thread = new Thread(() => SendData(clientChannel, watch)); // Setup server - server.ClientConnected += (sender, args) => + server.Connected += (sender, args) => { serverChannel = args.Channel; serverChannel.MessageReceived += (channel, messageReceived) => @@ -49,7 +49,7 @@ public void SocketThroughput2() clientConnected.Set(); }; - client.ServerConnected += (sender, args) => + client.Connected += (sender, args) => { clientChannel = args.Channel; @@ -58,7 +58,7 @@ public void SocketThroughput2() serverConnected.Set(); }; - var port = server.Start(); + var port = server.Start(IPAddress.Loopback.ToString()+":0"); client.Start(port); clientConnected.Wait(); diff --git a/test/TestAssets/SampleProjectWithOldTestHost/SampleProjectWithOldTestHost.csproj b/test/TestAssets/SampleProjectWithOldTestHost/SampleProjectWithOldTestHost.csproj index cc188af0ff..ea1534b9b3 100644 --- a/test/TestAssets/SampleProjectWithOldTestHost/SampleProjectWithOldTestHost.csproj +++ b/test/TestAssets/SampleProjectWithOldTestHost/SampleProjectWithOldTestHost.csproj @@ -20,4 +20,8 @@ + + + +