diff --git a/src/Microsoft.TestPlatform.CommunicationUtilities/Interfaces/ICommunicationManager.cs b/src/Microsoft.TestPlatform.CommunicationUtilities/Interfaces/ICommunicationManager.cs index 146ff80540..4e6b617b3e 100644 --- a/src/Microsoft.TestPlatform.CommunicationUtilities/Interfaces/ICommunicationManager.cs +++ b/src/Microsoft.TestPlatform.CommunicationUtilities/Interfaces/ICommunicationManager.cs @@ -105,6 +105,14 @@ public interface ICommunicationManager /// payload to be sent void SendMessage(string messageType, object payload); + /// + /// Writes message to the binary writer with payload + /// + /// Type of Message to be sent, for instance TestSessionStart + /// payload to be sent + /// version to be sent + void SendMessage(string messageType, object payload, int version); + /// /// Send serialized raw message /// diff --git a/src/Microsoft.TestPlatform.CommunicationUtilities/Interfaces/IDataSerializer.cs b/src/Microsoft.TestPlatform.CommunicationUtilities/Interfaces/IDataSerializer.cs index a5ec20fe11..5704a10e14 100644 --- a/src/Microsoft.TestPlatform.CommunicationUtilities/Interfaces/IDataSerializer.cs +++ b/src/Microsoft.TestPlatform.CommunicationUtilities/Interfaces/IDataSerializer.cs @@ -37,5 +37,14 @@ public interface IDataSerializer /// Payload of the message /// Raw Serialized message string SerializePayload(string messageType, object payload); + + /// + /// Serializes and creates a raw message given a message type and the object payload + /// + /// Message Type + /// Payload of the message + /// version to be sent + /// Raw Serialized message + string SerializePayload(string messageType, object payload, int version); } } diff --git a/src/Microsoft.TestPlatform.CommunicationUtilities/Interfaces/ITestRequestSender.cs b/src/Microsoft.TestPlatform.CommunicationUtilities/Interfaces/ITestRequestSender.cs index d5362332d0..553d9c297a 100644 --- a/src/Microsoft.TestPlatform.CommunicationUtilities/Interfaces/ITestRequestSender.cs +++ b/src/Microsoft.TestPlatform.CommunicationUtilities/Interfaces/ITestRequestSender.cs @@ -20,6 +20,11 @@ public interface ITestRequestSender : IDisposable /// Port Number of the communication channel int InitializeCommunication(); + /// + /// Used for protocol version check with TestHost + /// + void CheckVersionWithTestHost(); + /// /// Waits for Request Handler to be connected /// diff --git a/src/Microsoft.TestPlatform.CommunicationUtilities/JsonDataSerializer.cs b/src/Microsoft.TestPlatform.CommunicationUtilities/JsonDataSerializer.cs index 8123fc08d4..e749781816 100644 --- a/src/Microsoft.TestPlatform.CommunicationUtilities/JsonDataSerializer.cs +++ b/src/Microsoft.TestPlatform.CommunicationUtilities/JsonDataSerializer.cs @@ -6,7 +6,6 @@ namespace Microsoft.VisualStudio.TestPlatform.CommunicationUtilities using System.IO; using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.Interfaces; - using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.ObjectModel; using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.Serialization; using Newtonsoft.Json; @@ -20,26 +19,33 @@ public class JsonDataSerializer : IDataSerializer { private static JsonDataSerializer instance; - private static JsonSerializer serializer; + private static JsonSerializer payloadSerializer; + private static JsonSerializer payloadSerializer2; /// /// Prevents a default instance of the class from being created. /// private JsonDataSerializer() { - serializer = JsonSerializer.Create( - new JsonSerializerSettings - { - ContractResolver = new TestPlatformContractResolver(), - DateFormatHandling = DateFormatHandling.IsoDateFormat, - DateParseHandling = DateParseHandling.DateTimeOffset, - DateTimeZoneHandling = DateTimeZoneHandling.Utc, - TypeNameHandling = TypeNameHandling.None - }); + var jsonSettings = new JsonSerializerSettings + { + DateFormatHandling = DateFormatHandling.IsoDateFormat, + DateParseHandling = DateParseHandling.DateTimeOffset, + DateTimeZoneHandling = DateTimeZoneHandling.Utc, + TypeNameHandling = TypeNameHandling.None + }; + + payloadSerializer = JsonSerializer.Create(jsonSettings); + payloadSerializer2 = JsonSerializer.Create(jsonSettings); + + payloadSerializer.ContractResolver = new TestPlatformContractResolver1(); + payloadSerializer2.ContractResolver = new DefaultTestPlatformContractResolver(); + #if DEBUG // MemoryTraceWriter can help diagnose serialization issues. Enable it for // debug builds only. - serializer.TraceWriter = new MemoryTraceWriter(); + payloadSerializer.TraceWriter = new MemoryTraceWriter(); + payloadSerializer2.TraceWriter = new MemoryTraceWriter(); #endif } @@ -61,7 +67,9 @@ public static JsonDataSerializer Instance /// A instance. public Message DeserializeMessage(string rawMessage) { - return JsonConvert.DeserializeObject(rawMessage); + // Convert to VersionedMessage + // Message can be deserialized to VersionedMessage where version will be 0 + return JsonConvert.DeserializeObject(rawMessage); } /// @@ -74,17 +82,10 @@ public T DeserializePayload(Message message) { T retValue = default(T); - // TODO: Currently we use json serializer auto only for non-testmessage types - // CHECK: Can't we just use auto for everything - if (MessageType.TestMessage.Equals(message.MessageType)) - { - retValue = message.Payload.ToObject(); - } - else - { - retValue = message.Payload.ToObject(serializer); - } + var versionedMessage = message as VersionedMessage; + var serializer = this.GetPayloadSerializer(versionedMessage?.Version); + retValue = message.Payload.ToObject(serializer); return retValue; } @@ -92,10 +93,13 @@ public T DeserializePayload(Message message) /// Deserialize raw JSON to an object using the default serializer. /// /// JSON string. + /// Version of serializer to be used. /// Target type to deserialize. /// An instance of . - public T Deserialize(string json) + public T Deserialize(string json, int version = 1) { + var serializer = this.GetPayloadSerializer(version); + using (var stringReader = new StringReader(json)) using (var jsonReader = new JsonTextReader(stringReader)) { @@ -121,30 +125,41 @@ public string SerializeMessage(string messageType) /// Serialized message. public string SerializePayload(string messageType, object payload) { - JToken serializedPayload = null; - - // TODO: Currently we use json serializer auto only for non-testmessage types - // CHECK: Can't we just use auto for everything - if (MessageType.TestMessage.Equals(messageType)) - { - serializedPayload = JToken.FromObject(payload); - } - else - { - serializedPayload = JToken.FromObject(payload, serializer); - } + var serializedPayload = JToken.FromObject(payload, payloadSerializer); return JsonConvert.SerializeObject(new Message { MessageType = messageType, Payload = serializedPayload }); } + /// + /// Serialize a message with payload. + /// + /// Type of the message. + /// Payload for the message. + /// Version for the message. + /// Serialized message. + public string SerializePayload(string messageType, object payload, int version) + { + var serializer = this.GetPayloadSerializer(version); + var serializedPayload = JToken.FromObject(payload, serializer); + + var message = version == 1 ? + new Message { MessageType = messageType, Payload = serializedPayload } : + new VersionedMessage { MessageType = messageType, Version = version, Payload = serializedPayload }; + + return JsonConvert.SerializeObject(message); + } + /// /// Serialize an object to JSON using default serialization settings. /// /// Type of object to serialize. /// Instance of the object to serialize. + /// Version to be stamped. /// JSON string. - public string Serialize(T data) + public string Serialize(T data, int version = 1) { + var serializer = this.GetPayloadSerializer(version); + using (var stringWriter = new StringWriter()) using (var jsonWriter = new JsonTextWriter(stringWriter)) { @@ -153,5 +168,10 @@ public string Serialize(T data) return stringWriter.ToString(); } } + + private JsonSerializer GetPayloadSerializer(int? version) + { + return version == 2 ? payloadSerializer2 : payloadSerializer; + } } } diff --git a/src/Microsoft.TestPlatform.CommunicationUtilities/Messages/Message.cs b/src/Microsoft.TestPlatform.CommunicationUtilities/Messages/Message.cs index 1a98a1e1a4..2ea60f3454 100644 --- a/src/Microsoft.TestPlatform.CommunicationUtilities/Messages/Message.cs +++ b/src/Microsoft.TestPlatform.CommunicationUtilities/Messages/Message.cs @@ -6,6 +6,9 @@ namespace Microsoft.VisualStudio.TestPlatform.CommunicationUtilities using Newtonsoft.Json; using Newtonsoft.Json.Linq; + /// + /// Construct used for communication + /// public class Message { /// diff --git a/src/Microsoft.TestPlatform.CommunicationUtilities/Messages/MessageType.cs b/src/Microsoft.TestPlatform.CommunicationUtilities/Messages/MessageType.cs index 7b731afacf..e20d4fa303 100644 --- a/src/Microsoft.TestPlatform.CommunicationUtilities/Messages/MessageType.cs +++ b/src/Microsoft.TestPlatform.CommunicationUtilities/Messages/MessageType.cs @@ -38,6 +38,11 @@ public static class MessageType /// public const string VersionCheck = "ProtocolVersion"; + /// + /// Protocol Error + /// + public const string ProtocolError = "ProtocolError"; + /// /// The session start. /// diff --git a/src/Microsoft.TestPlatform.CommunicationUtilities/Messages/VersionedMessage.cs b/src/Microsoft.TestPlatform.CommunicationUtilities/Messages/VersionedMessage.cs new file mode 100644 index 0000000000..a48e59cdae --- /dev/null +++ b/src/Microsoft.TestPlatform.CommunicationUtilities/Messages/VersionedMessage.cs @@ -0,0 +1,17 @@ +// 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 +{ + /// + /// Construct with version used for communication + /// Introduced in 15.1.0 version and default message protocol v2 onwards. + /// + public class VersionedMessage : Message + { + /// + /// Gets or sets the version of the message + /// + public int Version { get; set; } + } +} \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.CommunicationUtilities/Resources/Resources.Designer.cs b/src/Microsoft.TestPlatform.CommunicationUtilities/Resources/Resources.Designer.cs index e5584b81e8..e2d5c5a448 100644 --- a/src/Microsoft.TestPlatform.CommunicationUtilities/Resources/Resources.Designer.cs +++ b/src/Microsoft.TestPlatform.CommunicationUtilities/Resources/Resources.Designer.cs @@ -107,5 +107,27 @@ internal static string DataCollectorUriForLogMessage return ResourceManager.GetString("DataCollectorUriForLogMessage", resourceCulture); } } + + /// + /// Looks up a localized string similar to Unexpected message received. Expected MessageType : {0} Actual MessageType: {1}. + /// + internal static string UnexpectedMessage + { + get + { + return ResourceManager.GetString("UnexpectedMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Protocol version check failed. Make sure test runner and host are compatible.. + /// + internal static string VersionCheckFailed + { + get + { + return ResourceManager.GetString("VersionCheckFailed", resourceCulture); + } + } } } diff --git a/src/Microsoft.TestPlatform.CommunicationUtilities/Resources/Resources.resx b/src/Microsoft.TestPlatform.CommunicationUtilities/Resources/Resources.resx index 13411e6979..92ba88351c 100644 --- a/src/Microsoft.TestPlatform.CommunicationUtilities/Resources/Resources.resx +++ b/src/Microsoft.TestPlatform.CommunicationUtilities/Resources/Resources.resx @@ -132,4 +132,10 @@ Unable to communicate with test host process. + + Unexpected message received. Expected MessageType : {0} Actual MessageType: {1} + + + Protocol version check failed. Make sure test runner and host are compatible. + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.CommunicationUtilities/Serialization/TestPlatformContractResolver.cs b/src/Microsoft.TestPlatform.CommunicationUtilities/Serialization/DefaultTestPlatformContractResolver.cs similarity index 96% rename from src/Microsoft.TestPlatform.CommunicationUtilities/Serialization/TestPlatformContractResolver.cs rename to src/Microsoft.TestPlatform.CommunicationUtilities/Serialization/DefaultTestPlatformContractResolver.cs index 30be74678e..3b8e3c526f 100644 --- a/src/Microsoft.TestPlatform.CommunicationUtilities/Serialization/TestPlatformContractResolver.cs +++ b/src/Microsoft.TestPlatform.CommunicationUtilities/Serialization/DefaultTestPlatformContractResolver.cs @@ -14,7 +14,7 @@ namespace Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.Serializati /// /// JSON contract resolver for mapping test platform types. /// - public class TestPlatformContractResolver : DefaultContractResolver + public class DefaultTestPlatformContractResolver : DefaultContractResolver { /// protected override JsonContract CreateContract(Type objectType) diff --git a/src/Microsoft.TestPlatform.CommunicationUtilities/Serialization/TestCaseConverter.cs b/src/Microsoft.TestPlatform.CommunicationUtilities/Serialization/TestCaseConverter.cs new file mode 100644 index 0000000000..03027c3393 --- /dev/null +++ b/src/Microsoft.TestPlatform.CommunicationUtilities/Serialization/TestCaseConverter.cs @@ -0,0 +1,51 @@ +// 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.Serialization +{ + using System; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Newtonsoft.Json; + + /// + /// Converter used by v1 protocol serializer to serialize TestCase object to and from v1 json + /// + public class TestCaseConverter : JsonConverter + { + /// + public override bool CanRead => false; + + /// + public override bool CanConvert(Type objectType) + { + return typeof(TestCase) == objectType; + } + + /// + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + // We do not need this as SetPropetyValue inside StoreKvpList will + // set the properties as expected. + throw new NotImplementedException(); + } + + /// + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + // P2 to P1 + var testCase = value as TestCase; + var properties = testCase.GetProperties(); + + writer.WriteStartObject(); + writer.WritePropertyName("Properties"); + writer.WriteStartArray(); + foreach (var property in properties) + { + serializer.Serialize(writer, property); + } + + writer.WriteEndArray(); + writer.WriteEndObject(); + } + } +} diff --git a/src/Microsoft.TestPlatform.CommunicationUtilities/Serialization/TestPlatformContractResolver1.cs b/src/Microsoft.TestPlatform.CommunicationUtilities/Serialization/TestPlatformContractResolver1.cs new file mode 100644 index 0000000000..0f50bd53f0 --- /dev/null +++ b/src/Microsoft.TestPlatform.CommunicationUtilities/Serialization/TestPlatformContractResolver1.cs @@ -0,0 +1,31 @@ +// 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.Serialization +{ + using System; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Newtonsoft.Json.Serialization; + + /// + /// JSON contract resolver for mapping test platform types for v1 serialization. + /// + public class TestPlatformContractResolver1 : DefaultTestPlatformContractResolver + { + /// + protected override JsonContract CreateContract(Type objectType) + { + var contract = base.CreateContract(objectType); + if (typeof(TestCase) == objectType) + { + contract.Converter = new TestCaseConverter(); + } + else if (typeof(TestResult) == objectType) + { + contract.Converter = new TestResultConverter(); + } + + return contract; + } + } +} \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.CommunicationUtilities/Serialization/TestResultConverter.cs b/src/Microsoft.TestPlatform.CommunicationUtilities/Serialization/TestResultConverter.cs new file mode 100644 index 0000000000..9824880490 --- /dev/null +++ b/src/Microsoft.TestPlatform.CommunicationUtilities/Serialization/TestResultConverter.cs @@ -0,0 +1,58 @@ +// 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.Serialization +{ + using System; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Newtonsoft.Json; + + /// + /// Converter used by v1 protocol serializer to serialize TestResult object to and from v1 json + /// + public class TestResultConverter : JsonConverter + { + /// + public override bool CanRead => false; + + /// + public override bool CanConvert(Type objectType) + { + return typeof(TestResult) == objectType; + } + + /// + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + // We do not need this as SetPropetyValue inside StoreKvpList will + // set the properties as expected. + throw new NotImplementedException(); + } + + /// + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + // P2 to P1 + var testResult = value as TestResult; + var properties = testResult.GetProperties(); + + writer.WriteStartObject(); + writer.WritePropertyName("TestCase"); + serializer.Serialize(writer, testResult.TestCase); + writer.WritePropertyName("Attachments"); + serializer.Serialize(writer, testResult.Attachments); + writer.WritePropertyName("Messages"); + serializer.Serialize(writer, testResult.Messages); + + writer.WritePropertyName("Properties"); + writer.WriteStartArray(); + foreach (var property in properties) + { + serializer.Serialize(writer, property); + } + + writer.WriteEndArray(); + writer.WriteEndObject(); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.CommunicationUtilities/SocketCommunicationManager.cs b/src/Microsoft.TestPlatform.CommunicationUtilities/SocketCommunicationManager.cs index 393a46c4a8..8a39ca8520 100644 --- a/src/Microsoft.TestPlatform.CommunicationUtilities/SocketCommunicationManager.cs +++ b/src/Microsoft.TestPlatform.CommunicationUtilities/SocketCommunicationManager.cs @@ -215,6 +215,18 @@ public void SendMessage(string messageType, object payload) this.WriteAndFlushToChannel(rawMessage); } + /// + /// Writes message to the binary writer with payload + /// + /// Type of Message to be sent, for instance TestSessionStart + /// payload to be sent + /// version to be sent + public void SendMessage(string messageType, object payload, int version) + { + var rawMessage = this.dataSerializer.SerializePayload(messageType, payload, version); + this.WriteAndFlushToChannel(rawMessage); + } + /// /// Send serialized raw message /// diff --git a/src/Microsoft.TestPlatform.CommunicationUtilities/TestRequestSender.cs b/src/Microsoft.TestPlatform.CommunicationUtilities/TestRequestSender.cs index 97d998b772..c0001b38e6 100644 --- a/src/Microsoft.TestPlatform.CommunicationUtilities/TestRequestSender.cs +++ b/src/Microsoft.TestPlatform.CommunicationUtilities/TestRequestSender.cs @@ -5,16 +5,15 @@ namespace Microsoft.VisualStudio.TestPlatform.CommunicationUtilities { using System; using System.Collections.Generic; + using System.Globalization; using System.IO; 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; /// @@ -28,6 +27,9 @@ public sealed class TestRequestSender : ITestRequestSender private IDataSerializer dataSerializer; + // TODO:sasin Change the version to 2 + private int highestNegotiatedVersion = 1; + /// /// Use to cancel blocking tasks associated with testhost process /// @@ -35,11 +37,19 @@ public sealed class TestRequestSender : ITestRequestSender private string clientExitErrorMessage; + /// + /// Initializes a new instance of the class. + /// public TestRequestSender() : this(new SocketCommunicationManager(), JsonDataSerializer.Instance) { } + /// + /// Initializes a new instance of the class. + /// + /// Communication Manager for sending and receiving messages. + /// Serializer for serialization and deserialization of the messages. internal TestRequestSender(ICommunicationManager communicationManager, IDataSerializer dataSerializer) { this.communicationManager = communicationManager; @@ -75,16 +85,41 @@ public void Close() EqtTrace.Info("Closing the connection"); } + /// + public void CheckVersionWithTestHost() + { + this.communicationManager.SendMessage(MessageType.VersionCheck, payload: this.highestNegotiatedVersion); + + var message = this.communicationManager.ReceiveMessage(); + + if (message.MessageType == MessageType.VersionCheck) + { + var protocolVersion = this.dataSerializer.DeserializePayload(message); + this.highestNegotiatedVersion = protocolVersion; + + EqtTrace.Info("TestRequestSender: VersionCheck Succeeded, NegotiatedVersion = {0}", this.highestNegotiatedVersion); + } + else if (message.MessageType == MessageType.ProtocolError) + { + // TODO : Payload for ProtocolError needs to finalized. + throw new TestPlatformException(string.Format(CultureInfo.CurrentUICulture, CommonResources.VersionCheckFailed)); + } + else + { + throw new TestPlatformException(string.Format(CultureInfo.CurrentUICulture, CommonResources.UnexpectedMessage, MessageType.VersionCheck, message.MessageType)); + } + } + /// public void InitializeDiscovery(IEnumerable pathToAdditionalExtensions, bool loadOnlyWellKnownExtensions) { - this.communicationManager.SendMessage(MessageType.DiscoveryInitialize, pathToAdditionalExtensions); + this.communicationManager.SendMessage(MessageType.DiscoveryInitialize, pathToAdditionalExtensions, version: this.highestNegotiatedVersion); } /// public void InitializeExecution(IEnumerable pathToAdditionalExtensions, bool loadOnlyWellKnownExtensions) { - this.communicationManager.SendMessage(MessageType.ExecutionInitialize, pathToAdditionalExtensions); + this.communicationManager.SendMessage(MessageType.ExecutionInitialize, pathToAdditionalExtensions, version: this.highestNegotiatedVersion); } /// @@ -92,7 +127,7 @@ public void DiscoverTests(DiscoveryCriteria discoveryCriteria, ITestDiscoveryEve { try { - this.communicationManager.SendMessage(MessageType.StartDiscovery, discoveryCriteria); + this.communicationManager.SendMessage(MessageType.StartDiscovery, discoveryCriteria, version: this.highestNegotiatedVersion); var isDiscoveryComplete = false; @@ -179,11 +214,18 @@ public void SendTestRunCancel() this.communicationManager.SendMessage(MessageType.CancelTestRun); } + /// + /// Send the Abort test run message + /// public void SendTestRunAbort() { this.communicationManager.SendMessage(MessageType.AbortTestRun); } + /// + /// Handles exit of the client process. + /// + /// Standard Error. public void OnClientProcessExit(string stdError) { this.clientExitErrorMessage = stdError; @@ -197,7 +239,7 @@ private void StartTestRunAndListenAndReportTestResults( { try { - this.communicationManager.SendMessage(messageType, payload); + this.communicationManager.SendMessage(messageType, payload, version: this.highestNegotiatedVersion); // This needs to happen asynchronously. Task.Run(() => this.ListenAndReportTestResults(eventHandler)); @@ -256,7 +298,8 @@ private void ListenAndReportTestResults(ITestRunEventsHandler testRunEventsHandl this.communicationManager.SendMessage( MessageType.LaunchAdapterProcessWithDebuggerAttachedCallback, - processId); + processId, + version: this.highestNegotiatedVersion); } } catch (IOException exception) diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Client/ProxyOperationManager.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/Client/ProxyOperationManager.cs index 962bc6e432..c10e88f367 100644 --- a/src/Microsoft.TestPlatform.CrossPlatEngine/Client/ProxyOperationManager.cs +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Client/ProxyOperationManager.cs @@ -18,6 +18,8 @@ namespace Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Client using Microsoft.VisualStudio.TestPlatform.Utilities; using CrossPlatEngineResources = Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Resources.Resources; + using System.Reflection; + using System.Linq; /// /// Base class for any operations that the client needs to drive through the engine. @@ -30,7 +32,7 @@ public abstract class ProxyOperationManager private bool initialized; private string testHostProcessStdError; - + private readonly string versionCheckPropertyName = "IsVersionCheckRequired"; #region Constructors /// @@ -114,22 +116,37 @@ public virtual void SetupChannel(IEnumerable sources) // Increase connection timeout when debugging is enabled. connTimeout = 5 * this.connectionTimeout; } - - this.initialized = true; - } - // Wait for a timeout for the client to connect. - if (!this.RequestSender.WaitForRequestHandlerConnection(connTimeout)) - { - var errorMsg = CrossPlatEngineResources.InitializationFailed; + // Wait for a timeout for the client to connect. + if (!this.RequestSender.WaitForRequestHandlerConnection(connTimeout)) + { + var errorMsg = CrossPlatEngineResources.InitializationFailed; + + if (!string.IsNullOrWhiteSpace(this.testHostProcessStdError.ToString())) + { + // Testhost failed with error + errorMsg = string.Format(CrossPlatEngineResources.TestHostExitedWithError, this.testHostProcessStdError); + } - if (!string.IsNullOrWhiteSpace(this.testHostProcessStdError)) + throw new TestPlatformException(string.Format(CultureInfo.CurrentUICulture, errorMsg)); + } + + // Handling special case for dotnet core projects with older test hosts + // Older test hosts are not aware of protocol version check + // Hence we should not be sending VersionCheck message to these test hosts + bool checkRequired = true; + var property = this.testHostManager.GetType().GetRuntimeProperties().FirstOrDefault(p => string.Equals(p.Name, versionCheckPropertyName, StringComparison.OrdinalIgnoreCase)); + if (property != null) + { + checkRequired = (bool)property.GetValue(this.testHostManager); + } + + if (checkRequired) { - // Testhost failed with error - errorMsg = string.Format(CrossPlatEngineResources.TestHostExitedWithError, this.testHostProcessStdError); + this.RequestSender.CheckVersionWithTestHost(); } - throw new TestPlatformException(string.Format(CultureInfo.CurrentUICulture, errorMsg)); + this.initialized = true; } } diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/EventHandlers/TestRequestHandler.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/EventHandlers/TestRequestHandler.cs index 4ee68c11bd..77a13a5f46 100644 --- a/src/Microsoft.TestPlatform.CrossPlatEngine/EventHandlers/TestRequestHandler.cs +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/EventHandlers/TestRequestHandler.cs @@ -31,6 +31,12 @@ public class TestRequestHandler : IDisposable, ITestRequestHandler private Action onAckMessageRecieved; + private int highestSupportedVersion = 2; + + // Set default to 1, if protocol version check does not happen + // that implies runner is using version 1 + private int protocolVersion = 1; + /// /// The timeout for the client to connect to the server. /// @@ -80,8 +86,21 @@ public void ProcessRequests(ITestHostManagerFactory testHostManagerFactory) do { var message = this.communicationManager.ReceiveMessage(); + 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("Logging TestHost Diagnostics in file: {0}", EqtTrace.LogFile)); + } + break; + case MessageType.DiscoveryInitialize: { EqtTrace.Info("Discovery Session Initialize."); @@ -212,20 +231,20 @@ public void Close() /// public void SendTestCases(IEnumerable discoveredTestCases) { - this.communicationManager.SendMessage(MessageType.TestCasesFound, discoveredTestCases); + this.communicationManager.SendMessage(MessageType.TestCasesFound, discoveredTestCases, this.protocolVersion); } /// public void SendTestRunStatistics(TestRunChangedEventArgs testRunChangedArgs) { - this.communicationManager.SendMessage(MessageType.TestRunStatsChange, testRunChangedArgs); + this.communicationManager.SendMessage(MessageType.TestRunStatsChange, testRunChangedArgs, this.protocolVersion); } /// public void SendLog(TestMessageLevel messageLevel, string message) { - var testMessagePayload = new TestMessagePayload { MessageLevel = messageLevel, Message = message }; - this.communicationManager.SendMessage(MessageType.TestMessage, testMessagePayload); + var testMessagePayload = new TestMessagePayload {MessageLevel = messageLevel,Message = message}; + this.communicationManager.SendMessage(MessageType.TestMessage, testMessagePayload, this.protocolVersion); } /// @@ -243,7 +262,7 @@ public void SendExecutionComplete( ExecutorUris = executorUris }; - this.communicationManager.SendMessage(MessageType.ExecutionComplete, payload); + this.communicationManager.SendMessage(MessageType.ExecutionComplete, payload, this.protocolVersion); } /// @@ -256,7 +275,7 @@ public void DiscoveryComplete(long totalTests, IEnumerable lastChunk, IsAborted = isAborted }; - this.communicationManager.SendMessage(MessageType.DiscoveryComplete, discoveryCompletePayload); + this.communicationManager.SendMessage(MessageType.DiscoveryComplete, discoveryCompletePayload, this.protocolVersion); } /// @@ -270,7 +289,7 @@ public int LaunchProcessWithDebuggerAttached(TestProcessStartInfo testProcessSta waitHandle.Set(); }; - this.communicationManager.SendMessage(MessageType.LaunchAdapterProcessWithDebuggerAttached, testProcessStartInfo); + this.communicationManager.SendMessage(MessageType.LaunchAdapterProcessWithDebuggerAttached, testProcessStartInfo, this.protocolVersion); waitHandle.WaitOne(); this.onAckMessageRecieved = null; diff --git a/src/Microsoft.TestPlatform.ObjectModel/TestCase.cs b/src/Microsoft.TestPlatform.ObjectModel/TestCase.cs index ba550f482f..ce70de97b8 100644 --- a/src/Microsoft.TestPlatform.ObjectModel/TestCase.cs +++ b/src/Microsoft.TestPlatform.ObjectModel/TestCase.cs @@ -77,7 +77,7 @@ public Object LocalExtensionData /// /// Gets or sets the id of the test case. /// - [IgnoreDataMember] + [DataMember] public Guid Id { get @@ -106,7 +106,7 @@ public Guid Id /// /// Gets or sets the fully qualified name of the test case. /// - [IgnoreDataMember] + [DataMember] public string FullyQualifiedName { get @@ -126,7 +126,7 @@ public string FullyQualifiedName /// /// Gets or sets the display name of the test case. /// - [IgnoreDataMember] + [DataMember] public string DisplayName { get @@ -143,7 +143,7 @@ public string DisplayName /// /// Gets or sets the Uri of the Executor to use for running this test. /// - [IgnoreDataMember] + [DataMember] public Uri ExecutorUri { get @@ -160,7 +160,7 @@ public Uri ExecutorUri /// /// Gets the test container source from which the test is discovered. /// - [IgnoreDataMember] + [DataMember] public string Source { get @@ -180,7 +180,7 @@ private set /// /// Gets or sets the source code file path of the test. /// - [IgnoreDataMember] + [DataMember] public string CodeFilePath { get @@ -197,7 +197,7 @@ public string CodeFilePath /// /// Gets or sets the line number of the test. /// - [IgnoreDataMember] + [DataMember] public int LineNumber { get @@ -244,6 +244,55 @@ private Guid GetTestId() } #endregion + + #region Protected Methods + + /// + /// Return TestProperty's value + /// + /// + protected override object ProtectedGetPropertyValue(TestProperty property, object defaultValue) + { + ValidateArg.NotNull(property, "property"); + + if (this.localStore.TryGetValue(property, out var value)) + { + return value; + } + + return base.ProtectedGetPropertyValue(property, defaultValue); + } + + /// + /// Set TestProperty's value + /// + protected override void ProtectedSetPropertyValue(TestProperty property, object value) + { + ValidateArg.NotNull(property, "property"); + + switch (property.Id) + { + case "TestCase.Id": + case "TestCase.ExecutorUri": + case "TestCase.FullyQualifiedName": + case "TestCase.DisplayName": + case "TestCase.Source": + case "TestCase.CodeFilePath": + case "TestCase.LineNumber": + if (property.ValidateValueCallback == null || property.ValidateValueCallback(value)) + { + this.localStore[property] = value; + } + else + { + throw new ArgumentException(property.Label); + } + return; + } + base.ProtectedSetPropertyValue(property, value); + } + + #endregion } /// diff --git a/src/Microsoft.TestPlatform.ObjectModel/TestObject.cs b/src/Microsoft.TestPlatform.ObjectModel/TestObject.cs index 2c954da006..423eb9e523 100644 --- a/src/Microsoft.TestPlatform.ObjectModel/TestObject.cs +++ b/src/Microsoft.TestPlatform.ObjectModel/TestObject.cs @@ -28,6 +28,11 @@ public abstract class TestObject /// private readonly Dictionary store; + /// + /// The store for all the local properties registered. + /// + protected readonly Dictionary localStore; + /// /// Property used for Json (de)serialization of store dictionary. Serialization of dictionaries /// by default doesn't provide the required object representation. List of KeyValuePair on the @@ -64,13 +69,23 @@ private List> StoreKeyValuePairs } } + /// + /// Returns the list of testproperties associated with the test object + /// + /// + public IEnumerable> GetProperties() + { + return this.localStore.Concat(this.store); + } + #endregion Fields #region Constructors protected TestObject() { - this.store = new Dictionary(); + this.store = new Dictionary(); + this.localStore = new Dictionary(); } [OnSerializing] @@ -104,7 +119,7 @@ public void CacheLazyValuesOnSerializing(StreamingContext context) /// public IEnumerable Properties { - get { return this.store.Keys; } + get { return this.localStore.Keys.Concat(this.store.Keys); } } /// @@ -124,7 +139,7 @@ public object GetPropertyValue(TestProperty property) defaultValue = Activator.CreateInstance(valueType); } - return this.PrivateGetPropertyValue(property, defaultValue); + return this.ProtectedGetPropertyValue(property, defaultValue); } /// @@ -168,7 +183,7 @@ public void SetPropertyValue(TestProperty property, LazyPropertyValue valu /// value to be set public void SetPropertyValue(TestProperty property, object value) { - this.PrivateSetPropertyValue(property, value); + this.ProtectedSetPropertyValue(property, value); } /// @@ -186,7 +201,6 @@ public void RemovePropertyValue(TestProperty property) } } - /// /// Returns TestProperty's value /// @@ -196,7 +210,7 @@ public T GetPropertyValue(TestProperty property, T defaultValue, CultureInfo ValidateArg.NotNull(property, "property"); ValidateArg.NotNull(culture, "culture"); - object objValue = this.PrivateGetPropertyValue(property, defaultValue); + object objValue = this.ProtectedGetPropertyValue(property, defaultValue); return ConvertPropertyTo(property, culture, objValue); } @@ -211,7 +225,7 @@ public void SetPropertyValue(TestProperty property, T value, CultureInfo cult object objValue = ConvertPropertyFrom(property, culture, value); - this.PrivateSetPropertyValue(property, objValue); + this.ProtectedSetPropertyValue(property, objValue); } /// @@ -224,7 +238,7 @@ public void SetPropertyValue(TestProperty property, LazyPropertyValue valu object objValue = ConvertPropertyFrom(property, culture, value); - this.PrivateSetPropertyValue(property, objValue); + this.ProtectedSetPropertyValue(property, objValue); } #endregion Property Values @@ -235,12 +249,12 @@ public void SetPropertyValue(TestProperty property, LazyPropertyValue valu /// Return TestProperty's value /// /// - private object PrivateGetPropertyValue(TestProperty property, object defaultValue) + protected virtual object ProtectedGetPropertyValue(TestProperty property, object defaultValue) { ValidateArg.NotNull(property, "property"); object value; - if (!this.store.TryGetValue(property, out value)) + if (!this.store.TryGetValue(property, out value) ) { value = defaultValue; } @@ -251,7 +265,7 @@ private object PrivateGetPropertyValue(TestProperty property, object defaultValu /// /// Set TestProperty's value /// - private void PrivateSetPropertyValue(TestProperty property, object value) + protected virtual void ProtectedSetPropertyValue(TestProperty property, object value) { ValidateArg.NotNull(property, "property"); diff --git a/src/Microsoft.TestPlatform.ObjectModel/TestResult.cs b/src/Microsoft.TestPlatform.ObjectModel/TestResult.cs index 12a7ac6ca8..2c0271c7d3 100644 --- a/src/Microsoft.TestPlatform.ObjectModel/TestResult.cs +++ b/src/Microsoft.TestPlatform.ObjectModel/TestResult.cs @@ -61,7 +61,7 @@ public TestResult(TestCase testCase) /// /// Gets or sets the outcome of a test case. /// - [IgnoreDataMember] + [DataMember] public TestOutcome Outcome { get @@ -78,7 +78,7 @@ public TestOutcome Outcome /// /// Gets or sets the exception message. /// - [IgnoreDataMember] + [DataMember] public string ErrorMessage { get @@ -95,7 +95,7 @@ public string ErrorMessage /// /// Gets or sets the exception stack trace. /// - [IgnoreDataMember] + [DataMember] public string ErrorStackTrace { get @@ -112,7 +112,7 @@ public string ErrorStackTrace /// /// Gets or sets the TestResult Display name. Used for Data Driven Test (i.e. Data Driven Test. E.g. InlineData in xUnit) /// - [IgnoreDataMember] + [DataMember] public string DisplayName { get @@ -139,7 +139,7 @@ public Collection Messages /// /// Gets or sets test result ComputerName. /// - [IgnoreDataMember] + [DataMember] public string ComputerName { get @@ -156,7 +156,7 @@ public string ComputerName /// /// Gets or sets the test result Duration. /// - [IgnoreDataMember] + [DataMember] public TimeSpan Duration { get @@ -173,7 +173,7 @@ public TimeSpan Duration /// /// Gets or sets the test result StartTime. /// - [IgnoreDataMember] + [DataMember] public DateTimeOffset StartTime { get @@ -190,7 +190,7 @@ public DateTimeOffset StartTime /// /// Gets or sets test result EndTime. /// - [IgnoreDataMember] + [DataMember] public DateTimeOffset EndTime { get @@ -207,7 +207,7 @@ public DateTimeOffset EndTime #endregion #region Methods - + /// public override string ToString() @@ -266,6 +266,56 @@ public override string ToString() } #endregion + + #region Protected Methods + + /// + /// Return TestProperty's value + /// + /// + protected override object ProtectedGetPropertyValue(TestProperty property, object defaultValue) + { + ValidateArg.NotNull(property, "property"); + + if (this.localStore.TryGetValue(property, out var value)) + { + return value; + } + + return base.ProtectedGetPropertyValue(property, defaultValue); + } + + /// + /// Set TestProperty's value + /// + protected override void ProtectedSetPropertyValue(TestProperty property, object value) + { + ValidateArg.NotNull(property, "property"); + + switch (property.Id) + { + case "TestResult.DisplayName": + case "TestResult.ComputerName": + case "TestResult.Outcome": + case "TestResult.Duration": + case "TestResult.StartTime": + case "TestResult.EndTime": + case "TestResult.ErrorMessage": + case "TestResult.ErrorStackTrace": + if (property.ValidateValueCallback == null || property.ValidateValueCallback(value)) + { + this.localStore[property] = value; + } + else + { + throw new ArgumentException(property.Label); + } + return; + } + base.ProtectedSetPropertyValue(property, value); + } + + #endregion } /// @@ -339,7 +389,7 @@ public static class TestResultProperties public static readonly TestProperty DisplayName = TestProperty.Register("TestResult.DisplayName", "TestResult Display Name", typeof(string), TestPropertyAttributes.Hidden, typeof(TestResult)); [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes")] - public static readonly TestProperty ComputerName = TestProperty.Register("TestResult.ComputerName", "Computer Name", string.Empty, string.Empty, typeof(string), ValidateComputerName, TestPropertyAttributes.None, typeof(TestResult)); + public static readonly TestProperty ComputerName = TestProperty.Register("TestResult.ComputerName", "Computer Name", typeof(string), TestPropertyAttributes.None, typeof(TestResult)); [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes")] public static readonly TestProperty Outcome = TestProperty.Register("TestResult.Outcome", "Outcome", string.Empty, string.Empty, typeof(TestOutcome), ValidateOutcome, TestPropertyAttributes.None, typeof(TestResult)); @@ -363,7 +413,7 @@ public static class TestResultProperties public static readonly TestProperty DisplayName = TestProperty.Register("TestResult.DisplayName", Resources.Resources.TestResultPropertyDisplayNameLabel, typeof(string), TestPropertyAttributes.Hidden, typeof(TestResult)); [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes")] - public static readonly TestProperty ComputerName = TestProperty.Register("TestResult.ComputerName", Resources.Resources.TestResultPropertyComputerNameLabel, string.Empty, string.Empty, typeof(string), ValidateComputerName, TestPropertyAttributes.None, typeof(TestResult)); + public static readonly TestProperty ComputerName = TestProperty.Register("TestResult.ComputerName", Resources.Resources.TestResultPropertyComputerNameLabel, typeof(string), TestPropertyAttributes.None, typeof(TestResult)); [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes")] public static readonly TestProperty Outcome = TestProperty.Register("TestResult.Outcome", Resources.Resources.TestResultPropertyOutcomeLabel, string.Empty, string.Empty, typeof(TestOutcome), ValidateOutcome, TestPropertyAttributes.None, typeof(TestResult)); @@ -383,12 +433,7 @@ public static class TestResultProperties [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes")] public static readonly TestProperty ErrorStackTrace = TestProperty.Register("TestResult.ErrorStackTrace", Resources.Resources.TestResultPropertyErrorStackTraceLabel, typeof(string), typeof(TestResult)); #endif - - private static bool ValidateComputerName(object value) - { - return !string.IsNullOrWhiteSpace((string)value); - } - + private static bool ValidateOutcome(object value) { return (TestOutcome)value <= TestOutcome.NotFound && (TestOutcome)value >= TestOutcome.None; diff --git a/src/Microsoft.TestPlatform.TestHostProvider/Hosting/DotnetTestHostManager.cs b/src/Microsoft.TestPlatform.TestHostProvider/Hosting/DotnetTestHostManager.cs index c6f41f6e46..927a0a8c25 100644 --- a/src/Microsoft.TestPlatform.TestHostProvider/Hosting/DotnetTestHostManager.cs +++ b/src/Microsoft.TestPlatform.TestHostProvider/Hosting/DotnetTestHostManager.cs @@ -62,6 +62,8 @@ public class DotnetTestHostManager : ITestRuntimeProvider private bool hostExitedEventRaised; + private string hostPackageVersion = "15.0.0"; + /// /// Initializes a new instance of the class. /// @@ -102,6 +104,11 @@ internal DotnetTestHostManager( /// public bool Shared => false; + /// + /// Gets a value indicating whether the test host supports protocol version check + /// + internal virtual bool IsVersionCheckRequired => !this.hostPackageVersion.StartsWith("15.0.0"); + /// /// Gets or sets the error length for runtime error stream. /// @@ -175,7 +182,7 @@ public void SetCustomLauncher(ITestHostLauncher customLauncher) } /// - public async Task LaunchTestHostAsync(TestProcessStartInfo testHostStartInfo) + public virtual async Task LaunchTestHostAsync(TestProcessStartInfo testHostStartInfo) { return await Task.Run(() => this.LaunchHost(testHostStartInfo), this.GetCancellationTokenSource().Token); } @@ -371,6 +378,7 @@ private string GetTestHostPath(string runtimeConfigDevPath, string depsFilePath, } testHostPath = Path.Combine(testhostPackage.Path, testHostPath); + this.hostPackageVersion = testhostPackage.Version; EqtTrace.Verbose("DotnetTestHostmanager: Relative path of testhost.dll with respect to package folder is {0}", testHostPath); } } diff --git a/src/testhost.x86/DefaultEngineInvoker.cs b/src/testhost.x86/DefaultEngineInvoker.cs index 06871a8212..2afe20d589 100644 --- a/src/testhost.x86/DefaultEngineInvoker.cs +++ b/src/testhost.x86/DefaultEngineInvoker.cs @@ -74,12 +74,6 @@ public void Invoke(IDictionary argsDictionary) EqtTrace.Info("DefaultEngineInvoker: Initialize communication on port number: '{0}'", portNumber); requestHandler.InitializeCommunication(portNumber); - // Can only do this after InitializeCommunication because TestHost cannot "Send Log" unless communications are initialized - if (!string.IsNullOrEmpty(EqtTrace.LogFile)) - { - requestHandler.SendLog(TestMessageLevel.Informational, string.Format("Logging TestHost Diagnostics in file: {0}", EqtTrace.LogFile)); - } - // Initialize DataCollection Communication if data collection port is provided. var dcPort = CommandLineArgumentsHelper.GetIntArgFromDict(argsDictionary, DataCollectionPortArgument); if (dcPort > 0) diff --git a/test/Microsoft.TestPlatform.CommunicationUtilities.PlatformTests/SocketCommunicationManagerTests.cs b/test/Microsoft.TestPlatform.CommunicationUtilities.PlatformTests/SocketCommunicationManagerTests.cs index 8e6088e93e..a922d6aeb4 100644 --- a/test/Microsoft.TestPlatform.CommunicationUtilities.PlatformTests/SocketCommunicationManagerTests.cs +++ b/test/Microsoft.TestPlatform.CommunicationUtilities.PlatformTests/SocketCommunicationManagerTests.cs @@ -22,6 +22,8 @@ public class SocketCommunicationManagerTests : IDisposable private const string TestDiscoveryStartMessageWithDummyPayload = "{\"MessageType\":\"TestDiscovery.Start\",\"Payload\":\"Dummy Payload\"}"; + private const string TestDiscoveryStartMessageWithVersionAndPayload = "{\"Version\":2,\"MessageType\":\"TestDiscovery.Start\",\"Payload\":\"Dummy Payload\"}"; + private const string DummyPayload = "Dummy Payload"; private readonly SocketCommunicationManager communicationManager; @@ -187,6 +189,16 @@ public async Task SendMessageWithPayloadShouldSerializeAndSendThePayload() Assert.AreEqual(TestDiscoveryStartMessageWithDummyPayload, this.ReadFromStream(client.GetStream())); } + [TestMethod] + public async Task SendMessageWithPayloadShouldSerializeAndSendThePayloadWithVersionStamped() + { + var client = await this.StartServerAndWaitForConnection(); + + this.communicationManager.SendMessage(MessageType.StartDiscovery, DummyPayload, 2); + + Assert.AreEqual(TestDiscoveryStartMessageWithVersionAndPayload, this.ReadFromStream(client.GetStream())); + } + [TestMethod] public async Task SendMessageWithRawMessageShouldNotSerializeThePayload() { @@ -217,12 +229,13 @@ public async Task ReceiveMessageShouldReceiveDeserializedMessage() public async Task ReceiveMessageAsyncShouldReceiveDeserializedMessage() { var client = await this.StartServerAndWaitForConnection(); - this.WriteToStream(client.GetStream(), TestDiscoveryStartMessageWithDummyPayload); + this.WriteToStream(client.GetStream(), TestDiscoveryStartMessageWithVersionAndPayload); var message = await this.communicationManager.ReceiveMessageAsync(CancellationToken.None); - - Assert.AreEqual(MessageType.StartDiscovery, message.MessageType); - Assert.AreEqual(DummyPayload, message.Payload); + var versionedMessage = message as VersionedMessage; + Assert.AreEqual(MessageType.StartDiscovery, versionedMessage.MessageType); + Assert.AreEqual(DummyPayload, versionedMessage.Payload); + Assert.AreEqual(2, versionedMessage.Version); } [TestMethod] diff --git a/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/DataCollectionTestCaseEventHandlerTests.cs b/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/DataCollectionTestCaseEventHandlerTests.cs index b4903358a7..4095a6fe8e 100644 --- a/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/DataCollectionTestCaseEventHandlerTests.cs +++ b/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/DataCollectionTestCaseEventHandlerTests.cs @@ -124,7 +124,8 @@ public void ProcessRequestsShouldProcessAfterTestCaseCompleteEvent() { var message = new Message(); message.MessageType = MessageType.DataCollectionTestEnd; - message.Payload = JToken.FromObject(new TestResultEventArgs(new VisualStudio.TestPlatform.ObjectModel.TestResult(new TestCase()))); + var testCase = new TestCase("hello", new Uri("world://how"), "1.dll"); + message.Payload = JToken.FromObject(new TestResultEventArgs(new VisualStudio.TestPlatform.ObjectModel.TestResult(testCase))); this.mockCommunicationManager.SetupSequence(x => x.ReceiveMessage()).Returns(message).Returns(new Message() { MessageType = MessageType.SessionEnd, Payload = "false" }); diff --git a/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/DataCollectionTestCaseEventSenderTests.cs b/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/DataCollectionTestCaseEventSenderTests.cs index 719e30e9f8..1f219fb612 100644 --- a/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/DataCollectionTestCaseEventSenderTests.cs +++ b/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/DataCollectionTestCaseEventSenderTests.cs @@ -23,6 +23,7 @@ public class DataCollectionTestCaseEventSenderTests { private DataCollectionTestCaseEventSender dataCollectionTestCaseEventSender; private Mock mockCommunicationManager; + private TestCase testCase = new TestCase("hello", new Uri("world://how"), "1.dll"); public DataCollectionTestCaseEventSenderTests() { @@ -90,8 +91,7 @@ public void CloseShouldThrowExceptionIfThrownByCommunicationManager() [TestMethod] public void SendTestCaseStartShouldSendMessageThroughCommunicationManager() { - var testCase = new TestCase(); - var testcaseStartEventArgs = new TestCaseStartEventArgs(testCase); + var testcaseStartEventArgs = new TestCaseStartEventArgs(this.testCase); this.dataCollectionTestCaseEventSender.SendTestCaseStart(testcaseStartEventArgs); this.mockCommunicationManager.Verify(x => x.SendMessage(MessageType.DataCollectionTestStart, testcaseStartEventArgs), Times.Once); @@ -100,8 +100,7 @@ public void SendTestCaseStartShouldSendMessageThroughCommunicationManager() [TestMethod] public void SendTestCaseStartShouldThrowExceptionIfThrownByCommunicationManager() { - var testCase = new TestCase(); - var testcaseStartEventArgs = new TestCaseStartEventArgs(testCase); + var testcaseStartEventArgs = new TestCaseStartEventArgs(this.testCase); this.mockCommunicationManager.Setup(x => x.SendMessage(MessageType.DataCollectionTestStart, testcaseStartEventArgs)).Throws(); Assert.ThrowsException(() => @@ -126,7 +125,6 @@ public void SendTestCaseEndShouldReturnAttachments() [TestMethod] public void SendTestCaseCompletedShouldThrowExceptionIfThrownByCommunicationManager() { - var testCase = new TestCase(); var testCaseEndEventArgs = new TestCaseEndEventArgs(); this.mockCommunicationManager.Setup(x => x.SendMessage(MessageType.DataCollectionTestEnd, It.IsAny())).Throws(); diff --git a/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/Serialization/TestCaseSerializationTests.cs b/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/Serialization/TestCaseSerializationTests.cs index b50a7a42fb..f28f1f0dda 100644 --- a/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/Serialization/TestCaseSerializationTests.cs +++ b/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/Serialization/TestCaseSerializationTests.cs @@ -28,6 +28,8 @@ public class TestCaseSerializationTests Traits = { new Trait("Priority", "0"), new Trait("Category", "unit") } }; + #region v1 Tests + [TestMethod] public void TestCaseJsonShouldContainAllPropertiesOnSerialization() { @@ -36,6 +38,7 @@ public void TestCaseJsonShouldContainAllPropertiesOnSerialization() // Use raw deserialization to validate basic properties dynamic data = JObject.Parse(json); dynamic properties = data["Properties"]; + Assert.AreEqual("TestCase.FullyQualifiedName", properties[0]["Key"]["Id"].Value); Assert.AreEqual("sampleTestClass.sampleTestCase", properties[0]["Value"].Value); Assert.AreEqual("TestCase.ExecutorUri", properties[1]["Key"]["Id"].Value); @@ -53,7 +56,7 @@ public void TestCaseJsonShouldContainAllPropertiesOnSerialization() // Traits require special handling with TestPlatformContract resolver. It should be null without it. Assert.AreEqual("TestObject.Traits", properties[7]["Key"]["Id"].Value); - Assert.IsNull(properties[7]["Key"]["Value"]); + Assert.IsNotNull(properties[7]["Value"]); } [TestMethod] @@ -120,29 +123,118 @@ public void TestCaseObjectShouldSerializeTraitsWithSpecialCharacters() Assert.AreEqual("[{\"Key\":\"t\",\"Value\":\"SDJDDHW>,:&^%//\\\\\\\\\\\\\\\\\"}]", properties[3]["Value"].ToString(Formatting.None)); } + #endregion + + #region v2 Tests + + [TestMethod] + public void TestCaseJsonShouldContainAllPropertiesOnSerializationV2() + { + var json = Serialize(testCase, 2); + + // Use raw deserialization to validate basic properties + dynamic data = JObject.Parse(json); + dynamic properties = data["Properties"]; + + // Traits require special handling with TestPlatformContract resolver. It should be null without it. + Assert.AreEqual("TestObject.Traits", properties[0]["Key"]["Id"].Value); + Assert.IsNotNull(properties[0]["Value"]); + + Assert.AreEqual("be78d6fc-61b0-4882-9d07-40d796fd96ce", data["Id"].Value); + Assert.AreEqual("sampleTestClass.sampleTestCase", data["FullyQualifiedName"].Value); + Assert.AreEqual("sampleTestCase", data["DisplayName"].Value); + Assert.AreEqual("sampleTest.dll", data["Source"].Value); + Assert.AreEqual("executor://sampleTestExecutor", data["ExecutorUri"].Value); + Assert.AreEqual("/user/src/testFile.cs", data["CodeFilePath"].Value); + Assert.AreEqual(999, data["LineNumber"].Value); + } + + [TestMethod] + public void TestCaseObjectShouldContainAllPropertiesOnDeserializationV2() + { + var json = "{\"Id\": \"be78d6fc-61b0-4882-9d07-40d796fd96ce\",\"FullyQualifiedName\": \"sampleTestClass.sampleTestCase\",\"DisplayName\": \"sampleTestCase\",\"ExecutorUri\": \"executor://sampleTestExecutor\",\"Source\": \"sampleTest.dll\",\"CodeFilePath\": \"/user/src/testFile.cs\", \"LineNumber\": 999," + + "\"Properties\": [{ \"Key\": { \"Id\": \"TestObject.Traits\", \"Label\": \"Traits\", \"Category\": \"\", \"Description\": \"\", \"Attributes\": 5, \"ValueType\": \"System.Collections.Generic.KeyValuePair`2[[System.String],[System.String]][]\"}, \"Value\": [{\"Key\": \"Priority\",\"Value\": \"0\"}, {\"Key\": \"Category\",\"Value\": \"unit\"}]}]}"; + + var test = Deserialize(json, 2); + + Assert.AreEqual(testCase.CodeFilePath, test.CodeFilePath); + Assert.AreEqual(testCase.DisplayName, test.DisplayName); + Assert.AreEqual(testCase.ExecutorUri, test.ExecutorUri); + Assert.AreEqual(testCase.FullyQualifiedName, test.FullyQualifiedName); + Assert.AreEqual(testCase.LineNumber, test.LineNumber); + Assert.AreEqual(testCase.Source, test.Source); + Assert.AreEqual(testCase.Traits.First().Name, test.Traits.First().Name); + Assert.AreEqual(testCase.Id, test.Id); + } + [TestMethod] - public void TestCaseObjectShouldDeserializeTraitsWithSpecialCharacters() + public void TestCaseObjectShouldSerializeTraitsWithSpecialCharactersV2() + { + var test = new TestCase("a.b", new Uri("uri://x"), @"/tmp/a.b.dll"); + test.Traits.Add("t", @"SDJDDHW>,:&^%//\\\\"); + + var json = Serialize(test, 2); + + // Use raw deserialization to validate basic properties + dynamic data = JObject.Parse(json); + dynamic properties = data["Properties"]; + Assert.AreEqual(@"TestObject.Traits", properties[0]["Key"]["Id"].Value); + Assert.AreEqual("[{\"Key\":\"t\",\"Value\":\"SDJDDHW>,:&^%//\\\\\\\\\\\\\\\\\"}]", properties[0]["Value"].ToString(Formatting.None)); + } + + [TestMethod] + public void TestCaseObjectShouldSerializeWindowsPathWithEscapingV2() + { + var test = new TestCase("a.b", new Uri("uri://x"), @"C:\Test\TestAssembly.dll"); + + var json = Serialize(test, 2); + + // Use raw deserialization to validate basic properties + dynamic data = JObject.Parse(json); + Assert.AreEqual(@"C:\Test\TestAssembly.dll", data["Source"].Value); + } + + [TestMethod] + public void TestCaseObjectShouldDeserializeEscapedWindowsPathV2() + { + var json = "{\"Id\":\"4e35ed85-a5e8-946e-fb14-0d3de2304e74\",\"FullyQualifiedName\":\"a.b\",\"DisplayName\":\"a.b\",\"ExecutorUri\":\"uri://x\",\"Source\":\"C:\\\\Test\\\\TestAssembly.dll\",\"CodeFilePath\":null,\"LineNumber\":-1,\"Properties\":[]}"; + + var test = Deserialize(json, 2); + + Assert.AreEqual(@"C:\Test\TestAssembly.dll", test.Source); + } + + #endregion + + #region Common Tests + + [TestMethod] + [DataRow(1)] + [DataRow(2)] + public void TestCaseObjectShouldDeserializeTraitsWithSpecialCharacters(int version) { var json = "{\"Properties\":[{\"Key\":{\"Id\":\"TestCase.FullyQualifiedName\",\"Label\":\"FullyQualifiedName\",\"Category\":\"\",\"Description\":\"\",\"Attributes\":1,\"ValueType\":\"System.String\"},\"Value\":\"a.b\"}," + "{\"Key\":{\"Id\":\"TestCase.ExecutorUri\",\"Label\":\"Executor Uri\",\"Category\":\"\",\"Description\":\"\",\"Attributes\":1,\"ValueType\":\"System.Uri\"},\"Value\":\"uri://x\"}," + "{\"Key\":{\"Id\":\"TestCase.Source\",\"Label\":\"Source\",\"Category\":\"\",\"Description\":\"\",\"Attributes\":0,\"ValueType\":\"System.String\"},\"Value\":\"/tmp/a.b.dll\"}," + "{\"Key\":{\"Id\":\"TestObject.Traits\",\"Label\":\"Traits\",\"Category\":\"\",\"Description\":\"\",\"Attributes\":5,\"ValueType\":\"System.Collections.Generic.KeyValuePair`2[[System.String],[System.String]][]\"},\"Value\":[{\"Key\":\"t\",\"Value\":\"SDJDDHW>,:&^%//\\\\\\\\\\\\\\\\\"}]}]}"; - var test = Deserialize(json); + var test = Deserialize(json, version); var traits = test.Traits.ToArray(); Assert.AreEqual(1, traits.Length); Assert.AreEqual(@"SDJDDHW>,:&^%//\\\\", traits[0].Value); } - private static string Serialize(T data) + #endregion + + private static string Serialize(T data, int version = 1) { - return JsonDataSerializer.Instance.Serialize(data); + return JsonDataSerializer.Instance.Serialize(data, version); } - private static T Deserialize(string json) + private static T Deserialize(string json, int version = 1) { - return JsonDataSerializer.Instance.Deserialize(json); + return JsonDataSerializer.Instance.Deserialize(json, version); } } } \ No newline at end of file diff --git a/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/Serialization/TestResultSerializationTests.cs b/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/Serialization/TestResultSerializationTests.cs index ca69dff452..bcc185ed90 100644 --- a/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/Serialization/TestResultSerializationTests.cs +++ b/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/Serialization/TestResultSerializationTests.cs @@ -21,6 +21,7 @@ public class TestResultSerializationTests new Uri("executor://sampleTestExecutor"), "sampleTest.dll"); + private static DateTimeOffset startTime = new DateTimeOffset(new DateTime(2007, 3, 10, 0, 0, 0, DateTimeKind.Utc)); private static TestResult testResult = new TestResult(testCase) { // Attachments = ? @@ -31,10 +32,12 @@ public class TestResultSerializationTests DisplayName = "sampleTestResult", ComputerName = "sampleComputerName", Duration = TimeSpan.MaxValue, - StartTime = DateTimeOffset.MinValue, + StartTime = startTime, EndTime = DateTimeOffset.MaxValue }; + #region v1 tests + [TestMethod] public void TestResultJsonShouldContainAllPropertiesOnSerialization() { @@ -58,7 +61,7 @@ public void TestResultJsonShouldContainAllPropertiesOnSerialization() // By default json.net converts DateTimes to current time zone Assert.AreEqual("TestResult.StartTime", properties[6]["Key"]["Id"].Value); - Assert.AreEqual(DateTimeOffset.MinValue.Year, ((DateTimeOffset)properties[6]["Value"].Value).Year); + Assert.AreEqual(startTime.Year, ((DateTimeOffset)properties[6]["Value"].Value).Year); Assert.AreEqual("TestResult.EndTime", properties[7]["Key"]["Id"].Value); Assert.AreEqual(DateTimeOffset.MaxValue.Year, ((DateTimeOffset)properties[7]["Value"].Value).Year); } @@ -66,7 +69,7 @@ public void TestResultJsonShouldContainAllPropertiesOnSerialization() [TestMethod] public void TestResultObjectShouldContainAllPropertiesOnDeserialization() { - var json = "{\"TestCase\":{\"Properties\":[{\"Key\":{\"Id\":\"TestCase.FullyQualifiedName\",\"Label\":\"FullyQualifiedName\",\"Category\":\"\",\"Description\":\"\",\"Attributes\":1,\"ValueType\":\"System.String\"},\"Value\":\"sampleTestClass.sampleTestCase\"},{\"Key\":{\"Id\":\"TestCase.ExecutorUri\",\"Label\":\"Executor Uri\",\"Category\":\"\",\"Description\":\"\",\"Attributes\":1,\"ValueType\":\"System.Uri\"},\"Value\":\"executor://sampleTestExecutor\"},{\"Key\":{\"Id\":\"TestCase.Source\",\"Label\":\"Source\",\"Category\":\"\",\"Description\":\"\",\"Attributes\":0,\"ValueType\":\"System.String\"},\"Value\":\"sampleTest.dll\"}]},\"Attachments\":[],\"Messages\":[],\"Properties\":[{\"Key\":{\"Id\":\"TestResult.Outcome\",\"Label\":\"Outcome\",\"Category\":\"\",\"Description\":\"\",\"Attributes\":0,\"ValueType\":\"Microsoft.VisualStudio.TestPlatform.ObjectModel.TestOutcome\"},\"Value\":1},{\"Key\":{\"Id\":\"TestResult.ErrorMessage\",\"Label\":\"Error Message\",\"Category\":\"\",\"Description\":\"\",\"Attributes\":0,\"ValueType\":\"System.String\"},\"Value\":\"sampleError\"},{\"Key\":{\"Id\":\"TestResult.ErrorStackTrace\",\"Label\":\"Error Stack Trace\",\"Category\":\"\",\"Description\":\"\",\"Attributes\":0,\"ValueType\":\"System.String\"},\"Value\":\"sampleStackTrace\"},{\"Key\":{\"Id\":\"TestResult.DisplayName\",\"Label\":\"TestResult Display Name\",\"Category\":\"\",\"Description\":\"\",\"Attributes\":1,\"ValueType\":\"System.String\"},\"Value\":\"sampleTestResult\"},{\"Key\":{\"Id\":\"TestResult.ComputerName\",\"Label\":\"Computer Name\",\"Category\":\"\",\"Description\":\"\",\"Attributes\":0,\"ValueType\":\"System.String\"},\"Value\":\"sampleComputerName\"},{\"Key\":{\"Id\":\"TestResult.Duration\",\"Label\":\"Duration\",\"Category\":\"\",\"Description\":\"\",\"Attributes\":0,\"ValueType\":\"System.TimeSpan\"},\"Value\":\"10675199.02:48:05.4775807\"},{\"Key\":{\"Id\":\"TestResult.StartTime\",\"Label\":\"Start Time\",\"Category\":\"\",\"Description\":\"\",\"Attributes\":0,\"ValueType\":\"System.DateTimeOffset\"},\"Value\":\"0001-01-01T00:00:00+00:00\"},{\"Key\":{\"Id\":\"TestResult.EndTime\",\"Label\":\"End Time\",\"Category\":\"\",\"Description\":\"\",\"Attributes\":0,\"ValueType\":\"System.DateTimeOffset\"},\"Value\":\"9999-12-31T23:59:59.9999999+00:00\"}]}"; + var json = "{\"TestCase\":{\"Properties\":[{\"Key\":{\"Id\":\"TestCase.FullyQualifiedName\",\"Label\":\"FullyQualifiedName\",\"Category\":\"\",\"Description\":\"\",\"Attributes\":1,\"ValueType\":\"System.String\"},\"Value\":\"sampleTestClass.sampleTestCase\"},{\"Key\":{\"Id\":\"TestCase.ExecutorUri\",\"Label\":\"Executor Uri\",\"Category\":\"\",\"Description\":\"\",\"Attributes\":1,\"ValueType\":\"System.Uri\"},\"Value\":\"executor://sampleTestExecutor\"},{\"Key\":{\"Id\":\"TestCase.Source\",\"Label\":\"Source\",\"Category\":\"\",\"Description\":\"\",\"Attributes\":0,\"ValueType\":\"System.String\"},\"Value\":\"sampleTest.dll\"}]},\"Attachments\":[],\"Messages\":[],\"Properties\":[{\"Key\":{\"Id\":\"TestResult.Outcome\",\"Label\":\"Outcome\",\"Category\":\"\",\"Description\":\"\",\"Attributes\":0,\"ValueType\":\"Microsoft.VisualStudio.TestPlatform.ObjectModel.TestOutcome\"},\"Value\":1},{\"Key\":{\"Id\":\"TestResult.ErrorMessage\",\"Label\":\"Error Message\",\"Category\":\"\",\"Description\":\"\",\"Attributes\":0,\"ValueType\":\"System.String\"},\"Value\":\"sampleError\"},{\"Key\":{\"Id\":\"TestResult.ErrorStackTrace\",\"Label\":\"Error Stack Trace\",\"Category\":\"\",\"Description\":\"\",\"Attributes\":0,\"ValueType\":\"System.String\"},\"Value\":\"sampleStackTrace\"},{\"Key\":{\"Id\":\"TestResult.DisplayName\",\"Label\":\"TestResult Display Name\",\"Category\":\"\",\"Description\":\"\",\"Attributes\":1,\"ValueType\":\"System.String\"},\"Value\":\"sampleTestResult\"},{\"Key\":{\"Id\":\"TestResult.ComputerName\",\"Label\":\"Computer Name\",\"Category\":\"\",\"Description\":\"\",\"Attributes\":0,\"ValueType\":\"System.String\"},\"Value\":\"sampleComputerName\"},{\"Key\":{\"Id\":\"TestResult.Duration\",\"Label\":\"Duration\",\"Category\":\"\",\"Description\":\"\",\"Attributes\":0,\"ValueType\":\"System.TimeSpan\"},\"Value\":\"10675199.02:48:05.4775807\"},{\"Key\":{\"Id\":\"TestResult.StartTime\",\"Label\":\"Start Time\",\"Category\":\"\",\"Description\":\"\",\"Attributes\":0,\"ValueType\":\"System.DateTimeOffset\"},\"Value\":\"2007-03-10T00:00:00+00:00\"},{\"Key\":{\"Id\":\"TestResult.EndTime\",\"Label\":\"End Time\",\"Category\":\"\",\"Description\":\"\",\"Attributes\":0,\"ValueType\":\"System.DateTimeOffset\"},\"Value\":\"9999-12-31T23:59:59.9999999+00:00\"}]}"; var test = Deserialize(json); @@ -108,14 +111,85 @@ public void TestResultObjectShouldDeserializeAttachments() Assert.AreEqual("sampleAttachment", result.Attachments[0].DisplayName); } - private static string Serialize(T data) + #endregion + + #region v2 Tests + + [TestMethod] + public void TestResultJsonShouldContainAllPropertiesOnSerializationV2() + { + var json = Serialize(testResult, 2); + + // Use raw deserialization to validate basic properties + dynamic data = JObject.Parse(json); + + Assert.AreEqual(1, data["Outcome"].Value); + Assert.AreEqual("sampleError", data["ErrorMessage"].Value); + Assert.AreEqual("sampleStackTrace", data["ErrorStackTrace"].Value); + Assert.AreEqual("sampleTestResult", data["DisplayName"].Value); + Assert.AreEqual("sampleComputerName", data["ComputerName"].Value); + Assert.AreEqual("10675199.02:48:05.4775807", data["Duration"].Value); + + // By default json.net converts DateTimes to current time zone + Assert.AreEqual(startTime.Year, ((DateTimeOffset)data["StartTime"].Value).Year); + Assert.AreEqual(DateTimeOffset.MaxValue.Year, ((DateTimeOffset)data["EndTime"].Value).Year); + } + + [TestMethod] + public void TestResultObjectShouldContainAllPropertiesOnDeserializationV2() + { + var json = "{\"TestCase\":{\"Id\":\"28e7a7ed-8fb9-05b7-5e90-4a8c52f32b5b\",\"FullyQualifiedName\":\"sampleTestClass.sampleTestCase\",\"DisplayName\":\"sampleTestClass.sampleTestCase\",\"ExecutorUri\":\"executor://sampleTestExecutor\",\"Source\":\"sampleTest.dll\",\"CodeFilePath\":null,\"LineNumber\":-1,\"Properties\":[]},\"Attachments\":[],\"Outcome\":1,\"ErrorMessage\":\"sampleError\",\"ErrorStackTrace\":\"sampleStackTrace\",\"DisplayName\":\"sampleTestResult\",\"Messages\":[],\"ComputerName\":\"sampleComputerName\",\"Duration\":\"10675199.02:48:05.4775807\",\"StartTime\":\"2007-03-10T00:00:00+00:00\",\"EndTime\":\"9999-12-31T23:59:59.9999999+00:00\",\"Properties\":[]}"; + + var test = Deserialize(json, 2); + + Assert.AreEqual(testResult.TestCase.Id, test.TestCase.Id); + Assert.AreEqual(testResult.Attachments.Count, test.Attachments.Count); + Assert.AreEqual(testResult.Messages.Count, test.Messages.Count); + + Assert.AreEqual(testResult.ComputerName, test.ComputerName); + Assert.AreEqual(testResult.DisplayName, test.DisplayName); + Assert.AreEqual(testResult.Duration, test.Duration); + Assert.AreEqual(testResult.EndTime, test.EndTime); + Assert.AreEqual(testResult.ErrorMessage, test.ErrorMessage); + Assert.AreEqual(testResult.ErrorStackTrace, test.ErrorStackTrace); + Assert.AreEqual(testResult.Outcome, test.Outcome); + Assert.AreEqual(testResult.StartTime, test.StartTime); + } + + [TestMethod] + public void TestResultObjectShouldSerializeAttachmentsV2() + { + var result = new TestResult(testCase); + result.Attachments.Add(new AttachmentSet(new Uri("http://dummyUri"), "sampleAttachment")); + var expectedJson = "{\"TestCase\":{\"Properties\":[{\"Key\":{\"Id\":\"TestCase.FullyQualifiedName\",\"Label\":\"FullyQualifiedName\",\"Category\":\"\",\"Description\":\"\",\"Attributes\":1,\"ValueType\":\"System.String\"},\"Value\":\"sampleTestClass.sampleTestCase\"},{\"Key\":{\"Id\":\"TestCase.ExecutorUri\",\"Label\":\"Executor Uri\",\"Category\":\"\",\"Description\":\"\",\"Attributes\":1,\"ValueType\":\"System.Uri\"},\"Value\":\"executor://sampleTestExecutor\"},{\"Key\":{\"Id\":\"TestCase.Source\",\"Label\":\"Source\",\"Category\":\"\",\"Description\":\"\",\"Attributes\":0,\"ValueType\":\"System.String\"},\"Value\":\"sampleTest.dll\"}]},\"Attachments\":[{\"Uri\":\"http://dummyUri\",\"DisplayName\":\"sampleAttachment\",\"Attachments\":[]}],\"Messages\":[],\"Properties\":[]}"; + + var json = Serialize(result); + + Assert.AreEqual(expectedJson, json); + } + + [TestMethod] + public void TestResultObjectShouldDeserializeAttachmentsV2() + { + var json = "{\"TestCase\":{\"Properties\":[{\"Key\":{\"Id\":\"TestCase.FullyQualifiedName\",\"Label\":\"FullyQualifiedName\",\"Category\":\"\",\"Description\":\"\",\"Attributes\":1,\"ValueType\":\"System.String\"},\"Value\":\"sampleTestClass.sampleTestCase\"},{\"Key\":{\"Id\":\"TestCase.ExecutorUri\",\"Label\":\"Executor Uri\",\"Category\":\"\",\"Description\":\"\",\"Attributes\":1,\"ValueType\":\"System.Uri\"},\"Value\":\"executor://sampleTestExecutor\"},{\"Key\":{\"Id\":\"TestCase.Source\",\"Label\":\"Source\",\"Category\":\"\",\"Description\":\"\",\"Attributes\":0,\"ValueType\":\"System.String\"},\"Value\":\"sampleTest.dll\"},{\"Key\":{\"Id\":\"TestCase.Id\",\"Label\":\"Id\",\"Category\":\"\",\"Description\":\"\",\"Attributes\":1,\"ValueType\":\"System.Guid\"},\"Value\":\"28e7a7ed-8fb9-05b7-5e90-4a8c52f32b5b\"}]},\"Attachments\":[{\"Uri\":\"http://dummyUri\",\"DisplayName\":\"sampleAttachment\",\"Attachments\":[]}],\"Messages\":[],\"Properties\":[]}"; + + var result = Deserialize(json); + + Assert.AreEqual(1, result.Attachments.Count); + Assert.AreEqual(new Uri("http://dummyUri"), result.Attachments[0].Uri); + Assert.AreEqual("sampleAttachment", result.Attachments[0].DisplayName); + } + + #endregion + + private static string Serialize(T data, int version = 1) { - return JsonDataSerializer.Instance.Serialize(data); + return JsonDataSerializer.Instance.Serialize(data, version); } - private static T Deserialize(string json) + private static T Deserialize(string json, int version = 1) { - return JsonDataSerializer.Instance.Deserialize(json); + return JsonDataSerializer.Instance.Deserialize(json, version); } } } \ No newline at end of file diff --git a/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/TestRequestSenderTests.cs b/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/TestRequestSenderTests.cs index acdd9edb9e..45ea11e6c6 100644 --- a/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/TestRequestSenderTests.cs +++ b/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/TestRequestSenderTests.cs @@ -26,6 +26,7 @@ public class TestRequestSenderTests private ITestRequestSender testRequestSender; private Mock mockCommunicationManager; private Mock mockDataSerializer; + private int version = 1; [TestInitialize] public void TestInit() @@ -71,13 +72,44 @@ public void DisposeShouldCallStopServerOnCommunicationManager() this.mockCommunicationManager.Verify(mc => mc.StopServer(), Times.Once); } + [TestMethod] + public void VersionCheckWithTestHostShouldCheckVersionIfVersionCheckPassesReturnTrue() + { + var message = new Message() { MessageType = MessageType.VersionCheck, Payload = this.version }; + this.mockCommunicationManager.Setup(mc => mc.ReceiveMessage()).Returns(message); + + this.testRequestSender.CheckVersionWithTestHost(); + + this.mockCommunicationManager.Verify(mc => mc.SendMessage(MessageType.VersionCheck, this.version), Times.Once); + } + + [TestMethod] + public void VersionCheckWithTestHostShouldBeAbleToReceiveProtocolErrorAndThrowException() + { + var message = new Message() { MessageType = MessageType.ProtocolError, Payload = this.version }; + this.mockCommunicationManager.Setup(mc => mc.ReceiveMessage()).Returns(message); + + var ex = Assert.ThrowsException(() => this.testRequestSender.CheckVersionWithTestHost()); + Assert.AreEqual("Protocol version check failed. Make sure test runner and host are compatible.", ex.Message); + } + + [TestMethod] + public void VersionCheckWithTestHostForInvalidMessageShouldThrowException() + { + var message = new Message() { MessageType = MessageType.TestCasesFound, Payload = null }; + this.mockCommunicationManager.Setup(mc => mc.ReceiveMessage()).Returns(message); + + var ex = Assert.ThrowsException(() => this.testRequestSender.CheckVersionWithTestHost()); + Assert.AreEqual("Unexpected message received. Expected MessageType : ProtocolVersion Actual MessageType: TestDiscovery.TestFound", ex.Message); + } + [TestMethod] public void InitializeDiscoveryShouldSendCommunicationMessageWithCorrectParameters() { var paths = new List() { "Hello", "World" }; this.testRequestSender.InitializeDiscovery(paths, false); - this.mockCommunicationManager.Verify(mc => mc.SendMessage(MessageType.DiscoveryInitialize, paths), Times.Once); + this.mockCommunicationManager.Verify(mc => mc.SendMessage(MessageType.DiscoveryInitialize, paths, this.version), Times.Once); } [TestMethod] @@ -86,7 +118,7 @@ public void InitializeExecutionShouldSendCommunicationMessageWithCorrectParamete var paths = new List() { "Hello", "World" }; this.testRequestSender.InitializeExecution(paths, true); - this.mockCommunicationManager.Verify(mc => mc.SendMessage(MessageType.ExecutionInitialize, paths), Times.Once); + this.mockCommunicationManager.Verify(mc => mc.SendMessage(MessageType.ExecutionInitialize, paths, this.version), Times.Once); } [TestMethod] @@ -120,7 +152,7 @@ public void DiscoverTestsShouldCallHandleDiscoveredTestsOnTestCaseEvent() this.testRequestSender.DiscoverTests(discoveryCriteria, mockHandler.Object); - this.mockCommunicationManager.Verify(mc => mc.SendMessage(MessageType.StartDiscovery, discoveryCriteria), Times.Once); + this.mockCommunicationManager.Verify(mc => mc.SendMessage(MessageType.StartDiscovery, discoveryCriteria, this.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)); @@ -157,7 +189,7 @@ public void DiscoverTestsShouldCallHandleLogMessageOnTestMessage() this.testRequestSender.DiscoverTests(discoveryCriteria, mockHandler.Object); - this.mockCommunicationManager.Verify(mc => mc.SendMessage(MessageType.StartDiscovery, discoveryCriteria), Times.Once); + this.mockCommunicationManager.Verify(mc => mc.SendMessage(MessageType.StartDiscovery, discoveryCriteria, this.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)); @@ -185,7 +217,7 @@ public void DiscoverTestsShouldCallHandleDiscoveryCompleteOnDiscoveryCompletion( this.testRequestSender.DiscoverTests(discoveryCriteria, mockHandler.Object); - this.mockCommunicationManager.Verify(mc => mc.SendMessage(MessageType.StartDiscovery, discoveryCriteria), Times.Once); + this.mockCommunicationManager.Verify(mc => mc.SendMessage(MessageType.StartDiscovery, discoveryCriteria, this.version), Times.Once); this.mockDataSerializer.Verify(ds => ds.DeserializeMessage(rawMessage), Times.Once); mockHandler.Verify(mh => mh.HandleDiscoveryComplete(1, null, false), Times.Once); mockHandler.Verify(mh => mh.HandleRawMessage(rawMessage), Times.Once); @@ -199,7 +231,7 @@ public void DiscoverTestsShouldHandleExceptionOnSendMessage() 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.mockCommunicationManager.Setup(cm => cm.SendMessage(MessageType.StartDiscovery, discoveryCriteria, this.version)) .Throws(exception); this.testRequestSender.DiscoverTests(discoveryCriteria, mockHandler.Object); @@ -277,7 +309,7 @@ public void StartTestRunWithSourcesShouldCallHandleTestRunStatsChange() waitHandle.WaitOne(); - this.mockCommunicationManager.Verify(mc => mc.SendMessage(MessageType.StartTestExecutionWithSources, runCriteria), Times.Once); + this.mockCommunicationManager.Verify(mc => mc.SendMessage(MessageType.StartTestExecutionWithSources, runCriteria, this.version), Times.Once); // One for run stats and another for runcomplete this.mockDataSerializer.Verify(ds => ds.DeserializeMessage(rawMessage), Times.Exactly(2)); @@ -318,7 +350,7 @@ public void StartTestRunWithTestsShouldCallHandleTestRunStatsChange() this.testRequestSender.StartTestRun(runCriteria, mockHandler.Object); waitHandle.WaitOne(); - this.mockCommunicationManager.Verify(mc => mc.SendMessage(MessageType.StartTestExecutionWithTests, runCriteria), Times.Once); + this.mockCommunicationManager.Verify(mc => mc.SendMessage(MessageType.StartTestExecutionWithTests, runCriteria, this.version), Times.Once); mockHandler.Verify(mh => mh.HandleTestRunStatsChange(testRunChangedArgs), Times.Once); mockHandler.Verify(mh => mh.HandleRawMessage(rawMessage), Times.AtLeastOnce); } @@ -353,7 +385,7 @@ public void StartTestRunShouldCallHandleLogMessageOnTestMessage() this.testRequestSender.StartTestRun(runCriteria, mockHandler.Object); waitHandle.WaitOne(); - this.mockCommunicationManager.Verify(mc => mc.SendMessage(MessageType.StartTestExecutionWithSources, runCriteria), Times.Once); + this.mockCommunicationManager.Verify(mc => mc.SendMessage(MessageType.StartTestExecutionWithSources, runCriteria, this.version), Times.Once); this.mockDataSerializer.Verify(ds => ds.DeserializeMessage(It.IsAny()), Times.Exactly(2)); mockHandler.Verify(mh => mh.HandleLogMessage(payload.MessageLevel, payload.Message), Times.Once); mockHandler.Verify(mh => mh.HandleRawMessage(rawMessage), Times.AtLeastOnce); @@ -398,12 +430,12 @@ public void StartTestRunShouldCallLaunchProcessWithDebuggerAndWaitForCallback() waitHandle.WaitOne(); - this.mockCommunicationManager.Verify(mc => mc.SendMessage(MessageType.StartTestExecutionWithSources, runCriteria), Times.Once); + this.mockCommunicationManager.Verify(mc => mc.SendMessage(MessageType.StartTestExecutionWithSources, runCriteria, this.version), Times.Once); 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()), Times.Once); + this.mockCommunicationManager.Verify(mc => mc.SendMessage(MessageType.LaunchAdapterProcessWithDebuggerAttachedCallback, It.IsAny(), this.version), Times.Once); } [TestMethod] @@ -430,7 +462,7 @@ public void StartTestRunShouldCallHandleTestRunCompleteOnRunCompletion() waitHandle.WaitOne(); - this.mockCommunicationManager.Verify(mc => mc.SendMessage(MessageType.StartTestExecutionWithTests, runCriteria), Times.Once); + this.mockCommunicationManager.Verify(mc => mc.SendMessage(MessageType.StartTestExecutionWithTests, runCriteria, this.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); diff --git a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/ProxyOperationManagerTests.cs b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/ProxyOperationManagerTests.cs index f7cb4096fa..738c577022 100644 --- a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/ProxyOperationManagerTests.cs +++ b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/ProxyOperationManagerTests.cs @@ -14,8 +14,9 @@ namespace TestPlatform.CrossPlatEngine.UnitTests.Client using Microsoft.VisualStudio.TestPlatform.ObjectModel; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Host; using Microsoft.VisualStudio.TestTools.UnitTesting; - + using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Hosting; using Moq; + using System.Threading.Tasks; [TestClass] public class ProxyOperationManagerTests @@ -127,12 +128,12 @@ public void SetupChannelShouldWaitForTestHostConnection() } [TestMethod] - public void SetupChannelShouldWaitForTestHostConnectionEvenIfConnectionIsInitialized() + public void SetupChannelShouldNotWaitForTestHostConnectionIfConnectionIsInitialized() { this.testOperationManager.SetupChannel(Enumerable.Empty()); this.testOperationManager.SetupChannel(Enumerable.Empty()); - this.mockRequestSender.Verify(rs => rs.WaitForRequestHandlerConnection(this.connectionTimeout), Times.Exactly(2)); + this.mockRequestSender.Verify(rs => rs.WaitForRequestHandlerConnection(this.connectionTimeout), Times.Exactly(1)); } [TestMethod] @@ -143,6 +144,43 @@ public void SetupChannelShouldThrowIfWaitForTestHostConnectionTimesOut() Assert.ThrowsException(() => this.testOperationManager.SetupChannel(Enumerable.Empty())); } + [TestMethod] + public void SetupChannelShouldCheckVersionWithTestHost() + { + this.testOperationManager.SetupChannel(Enumerable.Empty()); + this.mockRequestSender.Verify(rs => rs.CheckVersionWithTestHost(), Times.Once); + } + + [TestMethod] + public void SetupChannelShouldThrowExceptionIfVersionCheckFails() + { + // Make the version check fail + this.mockRequestSender.Setup(rs => rs.CheckVersionWithTestHost()).Throws(new TestPlatformException("Version check failed")); + Assert.ThrowsException(() => this.testOperationManager.SetupChannel(Enumerable.Empty())); + } + + [TestMethod] + public void SetupChannelForDotnetHostManagerWithIsVersionCheckRequiredFalseShouldNotCheckVersionWithTestHost() + { + var testHostManager = new TestableDotnetTestHostManager(false); + var operationManager = new TestableProxyOperationManager(this.mockRequestSender.Object, testHostManager, this.connectionTimeout); + + operationManager.SetupChannel(Enumerable.Empty()); + + this.mockRequestSender.Verify(rs => rs.CheckVersionWithTestHost(), Times.Never); + } + + [TestMethod] + public void SetupChannelForDotnetHostManagerWithIsVersionCheckRequiredTrueShouldCheckVersionWithTestHost() + { + var testHostManager = new TestableDotnetTestHostManager(true); + var operationManager = new TestableProxyOperationManager(this.mockRequestSender.Object, testHostManager, this.connectionTimeout); + + operationManager.SetupChannel(Enumerable.Empty()); + + this.mockRequestSender.Verify(rs => rs.CheckVersionWithTestHost(), Times.Once); + } + [TestMethod] [Ignore] //Not valid test anymore, since host providers now monitor test host themselves @@ -187,5 +225,29 @@ public TestableProxyOperationManager( { } } + + private class TestableDotnetTestHostManager : DotnetTestHostManager + { + private bool isVersionCheckRequired; + + public TestableDotnetTestHostManager(bool checkRequired) + { + this.isVersionCheckRequired = checkRequired; + } + + internal override bool IsVersionCheckRequired => this.isVersionCheckRequired; + + public override TestProcessStartInfo GetTestHostProcessStartInfo(IEnumerable sources, + IDictionary environmentVariables, + TestRunnerConnectionInfo connectionInfo) + { + return new TestProcessStartInfo(); + } + + public override async Task LaunchTestHostAsync(TestProcessStartInfo testHostStartInfo) + { + return await Task.Run(() => { return 0; }); + } + } } } diff --git a/test/Microsoft.TestPlatform.ObjectModel.UnitTests/TestObjectTests.cs b/test/Microsoft.TestPlatform.ObjectModel.UnitTests/TestObjectTests.cs index 65e3b601ee..e910b4ad26 100644 --- a/test/Microsoft.TestPlatform.ObjectModel.UnitTests/TestObjectTests.cs +++ b/test/Microsoft.TestPlatform.ObjectModel.UnitTests/TestObjectTests.cs @@ -7,20 +7,41 @@ namespace Microsoft.TestPlatform.ObjectModel.UnitTests using Microsoft.VisualStudio.TestPlatform.ObjectModel; using Microsoft.VisualStudio.TestTools.UnitTesting; + using System.Collections.Generic; + using System.Linq; [TestClass] public class TestObjectTests { + private static TestCase testCase = new TestCase( + "sampleTestClass.sampleTestCase", + new Uri("executor://sampleTestExecutor"), + "sampleTest.dll") + { + CodeFilePath = "/user/src/testFile.cs", + DisplayName = "sampleTestCase", + Id = new Guid("be78d6fc-61b0-4882-9d07-40d796fd96ce"), + Traits = { new Trait("Priority", "0"), new Trait("Category", "unit") } + }; + [TestMethod] public void TestCaseIdShouldReturnGuidWhenTestPropertiesIdIsSet() { - TestCase testCase = new TestCase("DummyNS.DummyClass.DummyTest", new Uri("executor://mstestadapter/v1"), "C:\tests.dll"); Guid expected = new Guid("{8167845C-9CDB-476F-9F2B-1B1C1FE01B7D}"); - testCase.SetPropertyValue(TestCaseProperties.Id, expected); - + testCase.Id = expected; var actual = testCase.Id; - Assert.AreEqual(expected, actual); } + + [TestMethod] + public void GetPropertiesShouldReturnListOfPropertiesInStore() + { + TestProperty tp = TestProperty.Register("dummyId", "dummyLabel", typeof(int), typeof(TestObjectTests)); + var kvp = new KeyValuePair(tp, 123); + testCase.SetPropertyValue(kvp.Key, kvp.Value); + + var properties = testCase.GetProperties().ToList(); + Assert.IsTrue(properties.Contains(kvp)); + } } } diff --git a/test/Microsoft.TestPlatform.PerformanceTests/ProtocolV1Tests.cs b/test/Microsoft.TestPlatform.PerformanceTests/ProtocolV1Tests.cs new file mode 100644 index 0000000000..7f78e23b16 --- /dev/null +++ b/test/Microsoft.TestPlatform.PerformanceTests/ProtocolV1Tests.cs @@ -0,0 +1,124 @@ +// 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.PerformanceTests +{ + using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestTools.UnitTesting; + using System; + using System.Diagnostics; + using System.IO; + + using TestResult = Microsoft.VisualStudio.TestPlatform.ObjectModel.TestResult; + + [TestClass] + public class ProtocolV1Tests + { + private static TestCase testCase = new TestCase( + "sampleTestClass.sampleTestCase", + new Uri("executor://sampleTestExecutor"), + "sampleTest.dll") + { + CodeFilePath = "/user/src/testFile.cs", + DisplayName = "sampleTestCase", + Id = new Guid("be78d6fc-61b0-4882-9d07-40d796fd96ce"), + LineNumber = 999, + Traits = { new Trait("Priority", "0"), new Trait("Category", "unit") } + }; + + private static DateTimeOffset startTime = new DateTimeOffset(new DateTime(2007, 3, 10, 0, 0, 0, DateTimeKind.Utc)); + + private static TestResult testResult = new TestResult(testCase) + { + // Attachments = ? + // Messages = ? + Outcome = TestOutcome.Passed, + ErrorMessage = "sampleError", + ErrorStackTrace = "sampleStackTrace", + DisplayName = "sampleTestResult", + ComputerName = "sampleComputerName", + Duration = TimeSpan.MaxValue, + StartTime = startTime, + EndTime = DateTimeOffset.MaxValue + }; + + [TestMethod] + public void TestCaseSerialize() + { + Serialize(testCase); + Stopwatch sw = Stopwatch.StartNew(); + + for (int i = 0; i < 10000; i++) + { + Serialize(testCase); + } + sw.Stop(); + + VerifyPerformanceResult("TestCaseSerialize1", 2000, sw.ElapsedMilliseconds); + } + + [TestMethod] + public void TestCaseDeserialize() + { + var json = Serialize(testCase); + Deserialize(json); + + Stopwatch sw = Stopwatch.StartNew(); + for (int i = 0; i < 10000; i++) + { + Deserialize(json); + } + sw.Stop(); + + VerifyPerformanceResult("TestCaseDeserialize1", 2000, sw.ElapsedMilliseconds); + } + + [TestMethod] + public void TestResultSerialize() + { + Serialize(testResult); + Stopwatch sw = Stopwatch.StartNew(); + + for (int i = 0; i < 10000; i++) + { + Serialize(testResult); + } + sw.Stop(); + + VerifyPerformanceResult("TestResultSerialize1", 2000, sw.ElapsedMilliseconds); + } + + [TestMethod] + public void TestResultDeserialize() + { + var json = Serialize(testResult); + Deserialize(json); + + Stopwatch sw = Stopwatch.StartNew(); + for (int i = 0; i < 10000; i++) + { + Deserialize(json); + } + sw.Stop(); + + VerifyPerformanceResult("TestResultDeserialize1", 3500, sw.ElapsedMilliseconds); + } + + private static string Serialize(T data, int version = 1) + { + return JsonDataSerializer.Instance.Serialize(data, version); + } + + private static T Deserialize(string json, int version = 1) + { + return JsonDataSerializer.Instance.Deserialize(json, version); + } + + private static void VerifyPerformanceResult(string scenario, long expectedElapsedTime, long elapsedTime) + { + Assert.IsTrue(elapsedTime < expectedElapsedTime, $"Scenario '{scenario}' doesn't match with expected elapsed time."); + File.AppendAllText(@"E:\ProtocolPerf.txt", $" {scenario} : " + elapsedTime); + } + } +} diff --git a/test/Microsoft.TestPlatform.PerformanceTests/ProtocolV2Tests.cs b/test/Microsoft.TestPlatform.PerformanceTests/ProtocolV2Tests.cs new file mode 100644 index 0000000000..6f677eb873 --- /dev/null +++ b/test/Microsoft.TestPlatform.PerformanceTests/ProtocolV2Tests.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 Microsoft.TestPlatform.PerformanceTests +{ + using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestTools.UnitTesting; + using System; + using System.Diagnostics; + using System.IO; + + using TestResult = Microsoft.VisualStudio.TestPlatform.ObjectModel.TestResult; + + [TestClass] + public class ProtocolV2Tests + { + private static TestCase testCase = new TestCase( + "sampleTestClass.sampleTestCase", + new Uri("executor://sampleTestExecutor"), + "sampleTest.dll") + { + CodeFilePath = "/user/src/testFile.cs", + DisplayName = "sampleTestCase", + Id = new Guid("be78d6fc-61b0-4882-9d07-40d796fd96ce"), + LineNumber = 999, + Traits = { new Trait("Priority", "0"), new Trait("Category", "unit") } + }; + + private static DateTimeOffset startTime = new DateTimeOffset(new DateTime(2007, 3, 10, 0, 0, 0, DateTimeKind.Utc)); + + private static TestResult testResult = new TestResult(testCase) + { + // Attachments = ? + // Messages = ? + Outcome = TestOutcome.Passed, + ErrorMessage = "sampleError", + ErrorStackTrace = "sampleStackTrace", + DisplayName = "sampleTestResult", + ComputerName = "sampleComputerName", + Duration = TimeSpan.MaxValue, + StartTime = startTime, + EndTime = DateTimeOffset.MaxValue + }; + + [TestMethod] + public void TestCaseSerialize2() + { + Serialize(testCase, 2); + Stopwatch sw = Stopwatch.StartNew(); + for (int i = 0; i < 10000; i++) + { + Serialize(testCase, 2); + } + sw.Stop(); + + VerifyPerformanceResult("TestCaseSerialize2", 2000, sw.ElapsedMilliseconds); + } + + [TestMethod] + public void TestCaseDeserialize2() + { + var json = Serialize(testCase, 2); + Deserialize(json, 2); + + Stopwatch sw = Stopwatch.StartNew(); + for (int i = 0; i < 10000; i++) + { + Deserialize(json, 2); + } + sw.Stop(); + + VerifyPerformanceResult("TestCaseDeserialize2", 2000, sw.ElapsedMilliseconds); + } + + [TestMethod] + public void TestResultSerialize2() + { + Serialize(testResult, 2); + Stopwatch sw = Stopwatch.StartNew(); + for (int i = 0; i < 10000; i++) + { + Serialize(testResult, 2); + } + sw.Stop(); + + VerifyPerformanceResult("TestResultSerialize2", 2000, sw.ElapsedMilliseconds); + } + + [TestMethod] + public void TestResultDeserialize2() + { + var json = Serialize(testResult, 2); + Deserialize(json, 2); + + Stopwatch sw = Stopwatch.StartNew(); + for (int i = 0; i < 10000; i++) + { + Deserialize(json, 2); + } + sw.Stop(); + + VerifyPerformanceResult("TestResultDeserialize2", 2000, sw.ElapsedMilliseconds); + } + + private static string Serialize(T data, int version = 1) + { + return JsonDataSerializer.Instance.Serialize(data, version); + } + + private static T Deserialize(string json, int version = 1) + { + return JsonDataSerializer.Instance.Deserialize(json, version); + } + + private static void VerifyPerformanceResult(string scenario, long expectedElapsedTime, long elapsedTime) + { + Assert.IsTrue(elapsedTime < expectedElapsedTime, $"Scenario '{scenario}' doesn't match with expected elapsed time."); + File.AppendAllText(@"E:\ProtocolPerf.txt", $" {scenario} : " + elapsedTime); + } + } +} diff --git a/test/TranslationLayer.UnitTests/VsTestConsoleRequestSenderTests.cs b/test/TranslationLayer.UnitTests/VsTestConsoleRequestSenderTests.cs index c07b0d3737..ee829d65c7 100644 --- a/test/TranslationLayer.UnitTests/VsTestConsoleRequestSenderTests.cs +++ b/test/TranslationLayer.UnitTests/VsTestConsoleRequestSenderTests.cs @@ -960,7 +960,7 @@ private static Message CreateMessage(string messageType, T payload) JsonSerializer.Create( new JsonSerializerSettings { - ContractResolver = new TestPlatformContractResolver(), + ContractResolver = new DefaultTestPlatformContractResolver(), TypeNameHandling = TypeNameHandling.None })) };