2016-04-29 19:11:27 +00:00
|
|
|
|
// Copyright (c) .NET Foundation and contributors. All rights reserved.
|
|
|
|
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
2015-12-09 17:57:45 +00:00
|
|
|
|
|
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Concurrent;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.IO;
|
|
|
|
|
using System.Net;
|
|
|
|
|
using System.Net.Sockets;
|
|
|
|
|
using System.Threading;
|
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
using Newtonsoft.Json;
|
|
|
|
|
using Xunit;
|
|
|
|
|
|
|
|
|
|
namespace Microsoft.DotNet.ProjectModel.Server.Tests
|
|
|
|
|
{
|
|
|
|
|
public class DthTestClient : IDisposable
|
|
|
|
|
{
|
|
|
|
|
private readonly string _hostId;
|
|
|
|
|
private readonly BinaryReader _reader;
|
|
|
|
|
private readonly BinaryWriter _writer;
|
|
|
|
|
private readonly NetworkStream _networkStream;
|
|
|
|
|
private readonly BlockingCollection<DthMessage> _messageQueue;
|
|
|
|
|
private readonly CancellationTokenSource _readCancellationToken;
|
|
|
|
|
|
|
|
|
|
// Keeps track of initialized project contexts
|
|
|
|
|
// REVIEW: This needs to be exposed if we ever create 2 clients in order to simulate how build
|
|
|
|
|
// works in visual studio
|
|
|
|
|
private readonly Dictionary<string, int> _projectContexts = new Dictionary<string, int>();
|
|
|
|
|
private int _nextContextId;
|
2016-01-29 09:49:56 +00:00
|
|
|
|
private readonly Socket _socket;
|
2015-12-09 17:57:45 +00:00
|
|
|
|
|
2016-04-28 17:01:30 +00:00
|
|
|
|
public DthTestClient(DthTestServer server)
|
2015-12-09 17:57:45 +00:00
|
|
|
|
{
|
2016-02-18 00:49:34 +00:00
|
|
|
|
// Avoid Socket exception 10006 on Linux
|
|
|
|
|
Thread.Sleep(100);
|
|
|
|
|
|
2016-01-29 09:49:56 +00:00
|
|
|
|
_socket = new Socket(AddressFamily.InterNetwork,
|
|
|
|
|
SocketType.Stream,
|
|
|
|
|
ProtocolType.Tcp);
|
2015-12-09 17:57:45 +00:00
|
|
|
|
|
2016-01-29 09:49:56 +00:00
|
|
|
|
_socket.Connect(new IPEndPoint(IPAddress.Loopback, server.Port));
|
2015-12-09 17:57:45 +00:00
|
|
|
|
|
|
|
|
|
_hostId = server.HostId;
|
|
|
|
|
|
2016-01-29 09:49:56 +00:00
|
|
|
|
_networkStream = new NetworkStream(_socket);
|
2015-12-09 17:57:45 +00:00
|
|
|
|
_reader = new BinaryReader(_networkStream);
|
|
|
|
|
_writer = new BinaryWriter(_networkStream);
|
|
|
|
|
|
|
|
|
|
_messageQueue = new BlockingCollection<DthMessage>();
|
|
|
|
|
|
|
|
|
|
_readCancellationToken = new CancellationTokenSource();
|
|
|
|
|
Task.Run(() => ReadMessage(_readCancellationToken.Token), _readCancellationToken.Token);
|
|
|
|
|
}
|
|
|
|
|
|
2016-05-02 18:32:24 +00:00
|
|
|
|
public void SendPayload(Project project, string messageType)
|
2015-12-09 17:57:45 +00:00
|
|
|
|
{
|
2016-05-02 18:32:24 +00:00
|
|
|
|
SendPayload(project.ProjectDirectory, messageType);
|
2015-12-09 17:57:45 +00:00
|
|
|
|
}
|
|
|
|
|
|
2016-05-02 18:32:24 +00:00
|
|
|
|
public void SendPayload(string projectPath, string messageType)
|
2015-12-09 17:57:45 +00:00
|
|
|
|
{
|
|
|
|
|
int contextId;
|
|
|
|
|
if (!_projectContexts.TryGetValue(projectPath, out contextId))
|
|
|
|
|
{
|
|
|
|
|
Assert.True(false, $"Unable to resolve context for {projectPath}");
|
|
|
|
|
}
|
|
|
|
|
|
2016-05-02 18:32:24 +00:00
|
|
|
|
SendPayload(contextId, messageType);
|
2015-12-09 17:57:45 +00:00
|
|
|
|
}
|
|
|
|
|
|
2016-05-02 18:32:24 +00:00
|
|
|
|
public void SendPayload(int contextId, string messageType)
|
2015-12-09 17:57:45 +00:00
|
|
|
|
{
|
2016-05-02 18:32:24 +00:00
|
|
|
|
SendPayload(contextId, messageType, new { });
|
2015-12-09 17:57:45 +00:00
|
|
|
|
}
|
|
|
|
|
|
2016-05-02 18:32:24 +00:00
|
|
|
|
public void SendPayload(int contextId, string messageType, object payload)
|
2015-12-09 17:57:45 +00:00
|
|
|
|
{
|
|
|
|
|
lock (_writer)
|
|
|
|
|
{
|
|
|
|
|
var message = new
|
|
|
|
|
{
|
|
|
|
|
ContextId = contextId,
|
|
|
|
|
HostId = _hostId,
|
|
|
|
|
MessageType = messageType,
|
|
|
|
|
Payload = payload
|
|
|
|
|
};
|
|
|
|
|
_writer.Write(JsonConvert.SerializeObject(message));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public int Initialize(string projectPath)
|
|
|
|
|
{
|
|
|
|
|
var contextId = _nextContextId++;
|
|
|
|
|
|
|
|
|
|
_projectContexts[projectPath] = contextId;
|
2016-05-02 18:32:24 +00:00
|
|
|
|
SendPayload(contextId, MessageTypes.Initialize, new { ProjectFolder = projectPath });
|
2015-12-09 17:57:45 +00:00
|
|
|
|
|
|
|
|
|
return contextId;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public int Initialize(string projectPath, int protocolVersion)
|
|
|
|
|
{
|
|
|
|
|
var contextId = _nextContextId++;
|
|
|
|
|
|
|
|
|
|
_projectContexts[projectPath] = contextId;
|
2016-05-02 18:32:24 +00:00
|
|
|
|
SendPayload(contextId, MessageTypes.Initialize, new { ProjectFolder = projectPath, Version = protocolVersion });
|
2015-12-09 17:57:45 +00:00
|
|
|
|
|
|
|
|
|
return contextId;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public int Initialize(string projectPath, int protocolVersion, string configuration)
|
|
|
|
|
{
|
|
|
|
|
var contextId = _nextContextId++;
|
|
|
|
|
|
|
|
|
|
_projectContexts[projectPath] = contextId;
|
2016-05-02 18:32:24 +00:00
|
|
|
|
SendPayload(contextId, MessageTypes.Initialize, new { ProjectFolder = projectPath, Version = protocolVersion, Configuration = configuration });
|
2015-12-09 17:57:45 +00:00
|
|
|
|
|
|
|
|
|
return contextId;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void SetProtocolVersion(int version)
|
|
|
|
|
{
|
2016-05-02 18:32:24 +00:00
|
|
|
|
SendPayload(0, MessageTypes.ProtocolVersion, new { Version = version });
|
2015-12-09 17:57:45 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public List<DthMessage> DrainMessage(int count)
|
|
|
|
|
{
|
|
|
|
|
var result = new List<DthMessage>();
|
|
|
|
|
while (count > 0)
|
|
|
|
|
{
|
|
|
|
|
result.Add(GetResponse(timeout: TimeSpan.FromSeconds(10)));
|
|
|
|
|
count--;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public List<DthMessage> DrainAllMessages()
|
|
|
|
|
{
|
|
|
|
|
return DrainAllMessages(TimeSpan.FromSeconds(10));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Read all messages from pipeline till timeout
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="timeout">The timeout</param>
|
|
|
|
|
/// <returns>All the messages in a list</returns>
|
|
|
|
|
public List<DthMessage> DrainAllMessages(TimeSpan timeout)
|
|
|
|
|
{
|
|
|
|
|
var result = new List<DthMessage>();
|
|
|
|
|
while (true)
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
result.Add(GetResponse(timeout));
|
|
|
|
|
}
|
|
|
|
|
catch (TimeoutException)
|
|
|
|
|
{
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
catch (Exception)
|
|
|
|
|
{
|
|
|
|
|
throw;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Read messages from pipeline until the first match
|
|
|
|
|
/// </summary>]
|
|
|
|
|
/// <param name="type">A message type</param>
|
|
|
|
|
/// <returns>The first match message</returns>
|
|
|
|
|
public DthMessage DrainTillFirst(string type)
|
|
|
|
|
{
|
|
|
|
|
return DrainTillFirst(type, TimeSpan.FromSeconds(10));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Read messages from pipeline until the first match
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="type">A message type</param>
|
|
|
|
|
/// <param name="timeout">Timeout for each read</param>
|
|
|
|
|
/// <returns>The first match message</returns>
|
|
|
|
|
public DthMessage DrainTillFirst(string type, TimeSpan timeout)
|
|
|
|
|
{
|
|
|
|
|
while (true)
|
|
|
|
|
{
|
|
|
|
|
var next = GetResponse(timeout);
|
|
|
|
|
if (next.MessageType == type)
|
|
|
|
|
{
|
|
|
|
|
return next;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Read messages from pipeline until the first match
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="type">A message type</param>
|
|
|
|
|
/// <param name="timeout">Timeout</param>
|
|
|
|
|
/// <param name="leadingMessages">All the messages read before the first match</param>
|
|
|
|
|
/// <returns>The first match</returns>
|
|
|
|
|
public DthMessage DrainTillFirst(string type, TimeSpan timeout, out List<DthMessage> leadingMessages)
|
|
|
|
|
{
|
|
|
|
|
leadingMessages = new List<DthMessage>();
|
|
|
|
|
while (true)
|
|
|
|
|
{
|
|
|
|
|
var next = GetResponse(timeout);
|
|
|
|
|
if (next.MessageType == type)
|
|
|
|
|
{
|
|
|
|
|
return next;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
leadingMessages.Add(next);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void Dispose()
|
|
|
|
|
{
|
|
|
|
|
_reader.Dispose();
|
|
|
|
|
_writer.Dispose();
|
|
|
|
|
_networkStream.Dispose();
|
|
|
|
|
_readCancellationToken.Cancel();
|
2016-04-22 22:01:56 +00:00
|
|
|
|
|
2016-03-21 21:55:02 +00:00
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
_socket.Shutdown(SocketShutdown.Both);
|
|
|
|
|
}
|
2016-03-21 22:18:28 +00:00
|
|
|
|
catch (SocketException ex)
|
2016-03-21 21:55:02 +00:00
|
|
|
|
{
|
|
|
|
|
// Swallow this error for now.
|
|
|
|
|
// This is a temporary fix for a random failure on CI. The issue happens on Windowx x86
|
|
|
|
|
// only.
|
2016-04-28 17:01:30 +00:00
|
|
|
|
Console.Error.WriteLine($"Exception thrown durning socket shutting down: {ex.SocketErrorCode}.");
|
2016-03-21 21:55:02 +00:00
|
|
|
|
}
|
2015-12-09 17:57:45 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void ReadMessage(CancellationToken cancellationToken)
|
|
|
|
|
{
|
|
|
|
|
while (true)
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
if (cancellationToken.IsCancellationRequested)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var content = _reader.ReadString();
|
|
|
|
|
var message = JsonConvert.DeserializeObject<DthMessage>(content);
|
|
|
|
|
|
|
|
|
|
_messageQueue.Add(message);
|
|
|
|
|
}
|
|
|
|
|
catch (IOException)
|
|
|
|
|
{
|
|
|
|
|
// swallow
|
|
|
|
|
}
|
|
|
|
|
catch (JsonSerializationException deserializException)
|
|
|
|
|
{
|
|
|
|
|
throw new InvalidOperationException(
|
|
|
|
|
$"Fail to deserailze data into {nameof(DthMessage)}.",
|
|
|
|
|
deserializException);
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
throw ex;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private DthMessage GetResponse(TimeSpan timeout)
|
|
|
|
|
{
|
|
|
|
|
DthMessage message;
|
|
|
|
|
|
|
|
|
|
if (_messageQueue.TryTake(out message, timeout))
|
|
|
|
|
{
|
|
|
|
|
return message;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
throw new TimeoutException($"Response time out after {timeout.TotalSeconds} seconds.");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|