diff --git a/TestPlatform.sln b/TestPlatform.sln index eac26bc1ef..67d17fdcdb 100644 --- a/TestPlatform.sln +++ b/TestPlatform.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 -VisualStudioVersion = 15.0.26005.1 +VisualStudioVersion = 15.0.26014.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{ED0C35EB-7F31-4841-A24F-8EB708FFA959}" EndProject @@ -118,6 +118,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PerfTestProject", "test\Tes EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SimpleDataCollector", "test\TestAssets\SimpleDataCollector\SimpleDataCollector.csproj", "{D62D754C-8F0A-406F-8BA7-E96C6FFA7C7C}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.TestPlatform.Protocol", "samples\Microsoft.TestPlatform.Protocol\Microsoft.TestPlatform.Protocol.csproj", "{97DD9467-B011-4736-AAC4-2C21BF554349}" +EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SimpleTestProject3", "test\TestAssets\SimpleTestProject3\SimpleTestProject3.csproj", "{82E75225-FA92-4F87-909D-039D62F96BFF}" EndProject Global @@ -646,6 +648,18 @@ Global {D62D754C-8F0A-406F-8BA7-E96C6FFA7C7C}.Release|x64.Build.0 = Release|Any CPU {D62D754C-8F0A-406F-8BA7-E96C6FFA7C7C}.Release|x86.ActiveCfg = Release|Any CPU {D62D754C-8F0A-406F-8BA7-E96C6FFA7C7C}.Release|x86.Build.0 = Release|Any CPU + {97DD9467-B011-4736-AAC4-2C21BF554349}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {97DD9467-B011-4736-AAC4-2C21BF554349}.Debug|Any CPU.Build.0 = Debug|Any CPU + {97DD9467-B011-4736-AAC4-2C21BF554349}.Debug|x64.ActiveCfg = Debug|x64 + {97DD9467-B011-4736-AAC4-2C21BF554349}.Debug|x64.Build.0 = Debug|x64 + {97DD9467-B011-4736-AAC4-2C21BF554349}.Debug|x86.ActiveCfg = Debug|x86 + {97DD9467-B011-4736-AAC4-2C21BF554349}.Debug|x86.Build.0 = Debug|x86 + {97DD9467-B011-4736-AAC4-2C21BF554349}.Release|Any CPU.ActiveCfg = Release|Any CPU + {97DD9467-B011-4736-AAC4-2C21BF554349}.Release|Any CPU.Build.0 = Release|Any CPU + {97DD9467-B011-4736-AAC4-2C21BF554349}.Release|x64.ActiveCfg = Release|x64 + {97DD9467-B011-4736-AAC4-2C21BF554349}.Release|x64.Build.0 = Release|x64 + {97DD9467-B011-4736-AAC4-2C21BF554349}.Release|x86.ActiveCfg = Release|x86 + {97DD9467-B011-4736-AAC4-2C21BF554349}.Release|x86.Build.0 = Release|x86 {82E75225-FA92-4F87-909D-039D62F96BFF}.Debug|Any CPU.ActiveCfg = Release|Any CPU {82E75225-FA92-4F87-909D-039D62F96BFF}.Debug|Any CPU.Build.0 = Release|Any CPU {82E75225-FA92-4F87-909D-039D62F96BFF}.Debug|x64.ActiveCfg = Release|Any CPU @@ -712,6 +726,7 @@ Global {A23E3408-D569-488E-A071-E1B3625C5F09} = {8DA7CBD9-F17E-41B6-90C4-CFF55848A25A} {57B182B8-9014-4C6D-B966-B464DE3127D5} = {8DA7CBD9-F17E-41B6-90C4-CFF55848A25A} {D62D754C-8F0A-406F-8BA7-E96C6FFA7C7C} = {8DA7CBD9-F17E-41B6-90C4-CFF55848A25A} + {97DD9467-B011-4736-AAC4-2C21BF554349} = {B9AB7A3D-4F63-48D2-86C0-70F52F6509AB} {82E75225-FA92-4F87-909D-039D62F96BFF} = {8DA7CBD9-F17E-41B6-90C4-CFF55848A25A} EndGlobalSection EndGlobal diff --git a/samples/Microsoft.TestPlatform.Protocol/Communication/JsonDataSerializer.cs b/samples/Microsoft.TestPlatform.Protocol/Communication/JsonDataSerializer.cs new file mode 100644 index 0000000000..b5800254a5 --- /dev/null +++ b/samples/Microsoft.TestPlatform.Protocol/Communication/JsonDataSerializer.cs @@ -0,0 +1,152 @@ +// 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.Protocol +{ + using System.IO; + + using Newtonsoft.Json; + using Newtonsoft.Json.Linq; + using Newtonsoft.Json.Serialization; + + /// + /// JsonDataSerializes serializes and deserializes data using Json format + /// + public class JsonDataSerializer + { + private static JsonDataSerializer instance; + + private static JsonSerializer serializer; + + /// + /// Prevents a default instance of the class from being created. + /// + private JsonDataSerializer() + { + serializer = JsonSerializer.Create( + new JsonSerializerSettings + { + DateFormatHandling = DateFormatHandling.IsoDateFormat, + DateParseHandling = DateParseHandling.DateTimeOffset, + DateTimeZoneHandling = DateTimeZoneHandling.Utc, + TypeNameHandling = TypeNameHandling.None + }); +#if DEBUG + // MemoryTraceWriter can help diagnose serialization issues. Enable it for + // debug builds only. + serializer.TraceWriter = new MemoryTraceWriter(); +#endif + } + + /// + /// Gets the JSON Serializer instance. + /// + public static JsonDataSerializer Instance + { + get + { + return instance ?? (instance = new JsonDataSerializer()); + } + } + + /// + /// Deserialize a from raw JSON text. + /// + /// JSON string. + /// A instance. + public Message DeserializeMessage(string rawMessage) + { + return JsonConvert.DeserializeObject(rawMessage); + } + + /// + /// Deserialize the for a message. + /// + /// A object. + /// Payload type. + /// The deserialized payload. + 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 (Microsoft.TestPlatform.Protocol.MessageType.TestMessage.Equals(message.MessageType)) + { + retValue = message.Payload.ToObject(); + } + else + { + retValue = message.Payload.ToObject(serializer); + } + + return retValue; + } + + /// + /// Deserialize raw JSON to an object using the default serializer. + /// + /// JSON string. + /// Target type to deserialize. + /// An instance of . + public T Deserialize(string json) + { + using (var stringReader = new StringReader(json)) + using (var jsonReader = new JsonTextReader(stringReader)) + { + return serializer.Deserialize(jsonReader); + } + } + + /// + /// Serialize an empty message. + /// + /// Type of the message. + /// Serialized message. + public string SerializeMessage(string messageType) + { + return JsonConvert.SerializeObject(new Message { MessageType = messageType }); + } + + /// + /// Serialize a message with payload. + /// + /// Type of the message. + /// Payload for the message. + /// 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); + } + + return JsonConvert.SerializeObject(new Message { MessageType = messageType, Payload = serializedPayload }); + } + + /// + /// Serialize an object to JSON using default serialization settings. + /// + /// Type of object to serialize. + /// Instance of the object to serialize. + /// JSON string. + public string Serialize(T data) + { + using (var stringWriter = new StringWriter()) + using (var jsonWriter = new JsonTextWriter(stringWriter)) + { + serializer.Serialize(jsonWriter, data); + + return stringWriter.ToString(); + } + } + } +} diff --git a/samples/Microsoft.TestPlatform.Protocol/Communication/Message.cs b/samples/Microsoft.TestPlatform.Protocol/Communication/Message.cs new file mode 100644 index 0000000000..732db13beb --- /dev/null +++ b/samples/Microsoft.TestPlatform.Protocol/Communication/Message.cs @@ -0,0 +1,30 @@ +// 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.Protocol +{ + using Newtonsoft.Json; + using Newtonsoft.Json.Linq; + + public class Message + { + /// + /// Gets or sets the message type. + /// + public string MessageType { get; set; } + + /// + /// Gets or sets the payload. + /// + public JToken Payload { get; set; } + + /// + /// To string implementation. + /// + /// The . + public override string ToString() + { + return "(" + MessageType + ") -> " + (Payload == null ? "null" : Payload.ToString(Formatting.Indented)); + } + } +} \ No newline at end of file diff --git a/samples/Microsoft.TestPlatform.Protocol/Communication/MessageType.cs b/samples/Microsoft.TestPlatform.Protocol/Communication/MessageType.cs new file mode 100644 index 0000000000..b37d567cd7 --- /dev/null +++ b/samples/Microsoft.TestPlatform.Protocol/Communication/MessageType.cs @@ -0,0 +1,142 @@ +// 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.Protocol +{ + /// + /// The message type. + /// + public static class MessageType + { + /// + /// The session start. + /// + public const string SessionStart = "TestSession.Start"; + + /// + /// The session end. + /// + public const string SessionEnd = "TestSession.Terminate"; + + /// + /// The is aborted. + /// + public const string SessionAbort = "TestSession.Abort"; + + /// + /// The session connected. + /// + public const string SessionConnected = "TestSession.Connected"; + + /// + /// Test Message + /// + public const string TestMessage = "TestSession.Message"; + + /// + /// Protocol Version + /// + public const string VersionCheck = "ProtocolVersion"; + + /// + /// The session start. + /// + public const string DiscoveryInitialize = "TestDiscovery.Initialize"; + + /// + /// The discovery started. + /// + public const string StartDiscovery = "TestDiscovery.Start"; + + /// + /// The test cases found. + /// + public const string TestCasesFound = "TestDiscovery.TestFound"; + + /// + /// The discovery complete. + /// + public const string DiscoveryComplete = "TestDiscovery.Completed"; + + /// + /// The session start. + /// + public const string ExecutionInitialize = "TestExecution.Initialize"; + + /// + /// Cancel the current test run + /// + public const string CancelTestRun = "TestExecution.Cancel"; + + /// + /// Cancel the current test run + /// + public const string AbortTestRun = "TestExecution.Abort"; + + /// + /// Start test execution. + /// + public const string StartTestExecutionWithSources = "TestExecution.StartWithSources"; + + /// + /// Start test execution. + /// + public const string StartTestExecutionWithTests = "TestExecution.StartWithTests"; + + /// + /// The test run stats change. + /// + public const string TestRunStatsChange = "TestExecution.StatsChange"; + + /// + /// The execution complete. + /// + public const string ExecutionComplete = "TestExecution.Completed"; + + /// + /// The message to get runner process startInfo for run all tests in given sources + /// + public const string GetTestRunnerProcessStartInfoForRunAll = "TestExecution.GetTestRunnerProcessStartInfoForRunAll"; + + /// + /// The message to get runner process startInfo for run selected tests + /// + public const string GetTestRunnerProcessStartInfoForRunSelected = "TestExecution.GetTestRunnerProcessStartInfoForRunSelected"; + + /// + /// CustomTestHostLaunch + /// + public const string CustomTestHostLaunch = "TestExecution.CustomTestHostLaunch"; + + /// + /// Custom Test Host launch callback + /// + public const string CustomTestHostLaunchCallback = "TestExecution.CustomTestHostLaunchCallback"; + + /// + /// Extensions Initialization + /// + public const string ExtensionsInitialize = "Extensions.Initialize"; + + /// + /// Start Test Run All Sources + /// + public const string TestRunAllSourcesWithDefaultHost = "TestExecution.RunAllWithDefaultHost"; + + /// + /// Start Test Run - Testcases + /// + public const string TestRunSelectedTestCasesDefaultHost = "TestExecution.RunSelectedWithDefaultHost"; + + /// + /// Launch Adapter Process With DebuggerAttached + /// + public const string LaunchAdapterProcessWithDebuggerAttached = "TestExecution.LaunchAdapterProcessWithDebuggerAttached"; + + /// + /// Launch Adapter Process With DebuggerAttached + /// + public const string LaunchAdapterProcessWithDebuggerAttachedCallback = "TestExecution.LaunchAdapterProcessWithDebuggerAttachedCallback"; + + } +} diff --git a/samples/Microsoft.TestPlatform.Protocol/Communication/SocketCommunicationManager.cs b/samples/Microsoft.TestPlatform.Protocol/Communication/SocketCommunicationManager.cs new file mode 100644 index 0000000000..0cc1b5a3ae --- /dev/null +++ b/samples/Microsoft.TestPlatform.Protocol/Communication/SocketCommunicationManager.cs @@ -0,0 +1,213 @@ +// 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.Protocol +{ + using System; + using System.IO; + using System.Net; + using System.Net.Sockets; + using System.Threading; + using System.Threading.Tasks; + + /// + /// Facilitates communication using sockets + /// + public class SocketCommunicationManager + { + /// + /// TCP Listener to host TCP channel and listen + /// + private TcpListener tcpListener; + + /// + /// Binary Writer to write to channel stream + /// + private BinaryWriter binaryWriter; + + /// + /// Binary reader to read from channel stream + /// + private BinaryReader binaryReader; + + /// + /// Serializer for the data objects + /// + private JsonDataSerializer dataSerializer; + + /// + /// Event used to maintain client connection state + /// + private ManualResetEvent clientConnectedEvent = new ManualResetEvent(false); + + /// + /// Sync object for sending messages + /// SendMessage over socket channel is NOT thread-safe + /// + private object sendSyncObject = new object(); + + /// + /// Stream to use read timeout + /// + private NetworkStream stream; + + private Socket socket; + + /// + /// The server stream read timeout constant (in microseconds). + /// + private const int StreamReadTimeout = 1000 * 1000; + + /// + /// Initializes a new instance of the class. + /// + public SocketCommunicationManager() : this(JsonDataSerializer.Instance) + { + } + + internal SocketCommunicationManager(JsonDataSerializer dataSerializer) + { + this.dataSerializer = dataSerializer; + } + + #region ServerMethods + + /// + /// Host TCP Socket Server and start listening + /// + /// + public int HostServer() + { + var endpoint = new IPEndPoint(IPAddress.Loopback, 0); + this.tcpListener = new TcpListener(endpoint); + + this.tcpListener.Start(); + var portNumber = ((IPEndPoint)this.tcpListener.LocalEndpoint).Port; + Console.WriteLine("Server started. Listening at port : {0}", portNumber); + return portNumber; + } + + /// + /// Accepts client async + /// + public async Task AcceptClientAsync() + { + if (this.tcpListener != null) + { + this.clientConnectedEvent.Reset(); + + var client = await this.tcpListener.AcceptTcpClientAsync(); + this.socket = client.Client; + this.stream = client.GetStream(); + this.binaryReader = new BinaryReader(this.stream); + this.binaryWriter = new BinaryWriter(this.stream); + + this.clientConnectedEvent.Set(); + + Console.WriteLine("Accepted Client request and set the flag"); + } + } + + /// + /// Waits for Client Connection + /// + /// Time to Wait for the connection + /// True if Client is connected, false otherwise + public bool WaitForClientConnection(int clientConnectionTimeout) + { + return this.clientConnectedEvent.WaitOne(clientConnectionTimeout); + } + + /// + /// Stop Listener + /// + public void StopServer() + { + this.tcpListener?.Stop(); + this.tcpListener = null; + this.binaryReader?.Dispose(); + this.binaryWriter?.Dispose(); + } + + #endregion + + /// + /// Writes message to the binary writer. + /// + /// Type of Message to be sent, for instance TestSessionStart + public void SendMessage(string messageType) + { + var serializedObject = this.dataSerializer.SerializeMessage(messageType); + this.WriteAndFlushToChannel(serializedObject); + } + + /// + /// Reads message from the binary reader + /// + /// Returns message read from the binary reader + public Message ReceiveMessage() + { + var rawMessage = this.ReceiveRawMessage(); + return this.dataSerializer.DeserializeMessage(rawMessage); + } + + /// + /// Writes message to the binary writer with payload + /// + /// Type of Message to be sent, for instance TestSessionStart + /// payload to be sent + public void SendMessage(string messageType, object payload) + { + var rawMessage = this.dataSerializer.SerializePayload(messageType, payload); + this.WriteAndFlushToChannel(rawMessage); + } + + /// + /// The send hand shake message. + /// + public void SendHandShakeMessage() + { + this.SendMessage(MessageType.SessionStart); + } + + /// + /// Reads message from the binary reader + /// + /// Raw message string + public string ReceiveRawMessage() + { + var rawMessage = this.binaryReader.ReadString(); + Console.WriteLine("\n=========== Receiving Message ==========="); + Console.WriteLine(rawMessage); + return rawMessage; + } + + /// + /// Deserializes the Message into actual TestPlatform objects + /// + /// The type of object to deserialize to. + /// Message object + /// TestPlatform object + public T DeserializePayload(Message message) + { + return this.dataSerializer.DeserializePayload(message); + } + + /// + /// Writes the data on socket and flushes the buffer + /// + /// message to write + private void WriteAndFlushToChannel(string rawMessage) + { + // Writing Message on binarywriter is not Thread-Safe + // Need to sync one by one to avoid buffer corruption + lock (this.sendSyncObject) + { + Console.WriteLine("\n=========== Sending Message ==========="); + Console.WriteLine(rawMessage); + this.binaryWriter?.Write(rawMessage); + this.binaryWriter?.Flush(); + } + } + } +} diff --git a/samples/Microsoft.TestPlatform.Protocol/Microsoft.TestPlatform.Protocol.csproj b/samples/Microsoft.TestPlatform.Protocol/Microsoft.TestPlatform.Protocol.csproj new file mode 100644 index 0000000000..2f1e4d453c --- /dev/null +++ b/samples/Microsoft.TestPlatform.Protocol/Microsoft.TestPlatform.Protocol.csproj @@ -0,0 +1,42 @@ + + + + net46 + Microsoft.TestPlatform.Protocol + Exe + win7-x64 + false + false + false + + + + + + + + + 4.1.1 + + + + + 8.0.3 + + + + + + + + + + 1.0.0-alpha-20161104-2 + All + + + + $(DefineConstants);RELEASE + + + \ No newline at end of file diff --git a/samples/Microsoft.TestPlatform.Protocol/Payload/DiscoveryRequestPayload.cs b/samples/Microsoft.TestPlatform.Protocol/Payload/DiscoveryRequestPayload.cs new file mode 100644 index 0000000000..e2a360e745 --- /dev/null +++ b/samples/Microsoft.TestPlatform.Protocol/Payload/DiscoveryRequestPayload.cs @@ -0,0 +1,26 @@ +// 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.Protocol +{ + using System.Collections.Generic; + using System.Runtime.Serialization; + + /// + /// Class used to define the DiscoveryRequestPayload sent by the Vstest.console translation layers into design mode + /// + public class DiscoveryRequestPayload + { + /// + /// Settings used for the discovery request. + /// + [DataMember] + public IEnumerable Sources { get; set; } + + /// + /// Settings used for the discovery request. + /// + [DataMember] + public string RunSettings { get; set; } + } +} diff --git a/samples/Microsoft.TestPlatform.Protocol/Payload/TestRunRequestPayload.cs b/samples/Microsoft.TestPlatform.Protocol/Payload/TestRunRequestPayload.cs new file mode 100644 index 0000000000..5cebd391c6 --- /dev/null +++ b/samples/Microsoft.TestPlatform.Protocol/Payload/TestRunRequestPayload.cs @@ -0,0 +1,44 @@ +// 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.Protocol +{ + using System.Collections.Generic; + using System.Runtime.Serialization; + + /// + /// Class used to define the TestRunRequestPayload sent to Vstest.console in design mode + /// + public class TestRunRequestPayload + { + /// + /// Gets or sets the sources for the test run request. + /// + [DataMember] + public List Sources { get; set; } + + /// + /// Gets or sets the test cases for the test run request. + /// + [DataMember] + public dynamic TestCases { get; set; } + + /// + /// Gets or sets the settings used for the test run request. + /// + [DataMember] + public string RunSettings { get; set; } + + /// + /// Settings used for the Run request. + /// + [DataMember] + public bool KeepAlive { get; set; } + + /// + /// Is Debugging enabled + /// + [DataMember] + public bool DebuggingEnabled { get; set; } + } +} diff --git a/samples/Microsoft.TestPlatform.Protocol/Program.cs b/samples/Microsoft.TestPlatform.Protocol/Program.cs new file mode 100644 index 0000000000..938013dc08 --- /dev/null +++ b/samples/Microsoft.TestPlatform.Protocol/Program.cs @@ -0,0 +1,201 @@ +//// 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.Protocol +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Globalization; + using System.IO; + using System.Threading; + + public class Program + { + private const string PORT_ARGUMENT = "/port:{0}"; + private const string PARENT_PROCESSID_ARGUMENT = "/parentprocessid:{0}"; + + private static SocketCommunicationManager communicationManager; + private static JsonDataSerializer dataSerializer = JsonDataSerializer.Instance; + + public static void Main(string[] args) + { + if(args == null || args.Length < 1) + { + Console.WriteLine("Please provide appropriate arguments. Arguments can be passed as following:"); + Console.WriteLine("Microsoft.TestPlatform.Protocol.exe --testassembly:\"[assemblyPath]\" --operation:\"[RunAll|RunSelected|Discovery]\" --testadapterpath:\"[path]\""); + Console.WriteLine("or Microsoft.TestPlatform.Protocol.exe -a:\"[assemblyPath]\" -o:\"[RunAll|RunSelected|Discovery]\" -p:\"[path]\" \n"); + } + + var executingLocation = Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName); + + // Default values + var testAssembly = Path.Combine(executingLocation, "UnitTestProject.dll"); + string testadapterPath = null; + string operation = "Discovery"; + var separator = new char[] { ':' }; + foreach (var arg in args) + { + if (arg.StartsWith("-p:") || arg.StartsWith("--testadapterpath:")) + { + testadapterPath = arg.Split(separator, 2)[1]; + } + else if (arg.StartsWith("-a:") || arg.StartsWith("--testassembly:")) + { + testAssembly = arg.Split(separator,2)[1]; + } + else if (arg.StartsWith("-o:") || arg.StartsWith("--operation:")) + { + operation = arg.Split(separator, 2)[1]; + } + } + + Console.WriteLine("TestAdapter Path : {0}", testadapterPath); + Console.WriteLine("TestAssembly Path : {0}", testAssembly); + Console.WriteLine("Operation : {0}", operation); + + var processManager = new RunnerProcessManager(); + communicationManager = new SocketCommunicationManager(); + + // Start the server + var port = communicationManager.HostServer(); + + //Start runner exe and wait for the connection + string parentProcessIdArgs = string.Format(CultureInfo.InvariantCulture, PARENT_PROCESSID_ARGUMENT, Process.GetCurrentProcess().Id); + string portArgs = string.Format(CultureInfo.InvariantCulture, PORT_ARGUMENT, port); + processManager.StartProcess(new string[2] { parentProcessIdArgs, portArgs }); + + communicationManager.AcceptClientAsync(); + communicationManager.WaitForClientConnection(Timeout.Infinite); + HandShakeWithVsTestConsole(); + + //Actual operation + dynamic discoveredTestCases; + switch (operation.ToLower()) + { + case "discovery": + discoveredTestCases = DiscoverTests(testadapterPath, testAssembly); + break; + + case "runselected": + discoveredTestCases = DiscoverTests(testadapterPath, testAssembly); + RunSelectedTests(discoveredTestCases); + break; + + case "runall": + default: + RunAllTests(new List() { testAssembly }); + break; + } + } + + static void HandShakeWithVsTestConsole() + { + //HandShake with vstest.console + Console.WriteLine("=========== HandShake with vstest.console =========="); + var message = communicationManager.ReceiveMessage(); + if (message.MessageType == MessageType.SessionConnected) + { + //Version Check + communicationManager.SendMessage(MessageType.VersionCheck); + message = communicationManager.ReceiveMessage(); + + if (message.MessageType == MessageType.VersionCheck) + { + var version = JsonDataSerializer.Instance.DeserializePayload(message); + + var success = version == 1; + Console.WriteLine("Version Success: {0}", success); + } + } + } + + static dynamic DiscoverTests(string testadapterPath, string testAssembly) + { + Console.WriteLine("Starting Operation : Discovery"); + + // Intialize the extensions + if (testadapterPath != null) + { + communicationManager.SendMessage(MessageType.ExtensionsInitialize, new List() { testadapterPath }); + } + + // Start Discovery + communicationManager.SendMessage( + MessageType.StartDiscovery, + new DiscoveryRequestPayload() { Sources = new List() { testAssembly }, RunSettings = null }); + var isDiscoveryComplete = false; + + dynamic testCases = null; + + while (!isDiscoveryComplete) + { + var message = communicationManager.ReceiveMessage(); + + if (string.Equals(MessageType.TestCasesFound, message.MessageType)) + { + // Handle discovered tests here. + testCases = (JsonDataSerializer.Instance.DeserializePayload(message)); + } + else if (string.Equals(MessageType.DiscoveryComplete, message.MessageType)) + { + dynamic discoveryCompletePayload = + JsonDataSerializer.Instance.DeserializePayload(message); + + //Handle discovery complete here + isDiscoveryComplete = true; + } + else if (string.Equals(MessageType.TestMessage, message.MessageType)) + { + var testMessagePayload = dataSerializer.DeserializePayload(message); + // Handle messages here. + } + } + + return testCases; + } + + static void RunAllTests(List sources) + { + Console.WriteLine("Starting Operation: RunAll"); + communicationManager.SendMessage(MessageType.TestRunAllSourcesWithDefaultHost, new TestRunRequestPayload() { Sources = sources, RunSettings = null }); + RecieveRunMesagesAndHandleRunComplete(); + } + + static void RunSelectedTests(dynamic testCases) + { + Console.WriteLine("Starting Operation: RunSelected"); + communicationManager.SendMessage(MessageType.TestRunSelectedTestCasesDefaultHost, new TestRunRequestPayload() { TestCases = testCases, RunSettings = null }); + RecieveRunMesagesAndHandleRunComplete(); + } + + static void RecieveRunMesagesAndHandleRunComplete() + { + var isTestRunComplete = false; + + while (!isTestRunComplete) + { + var message = communicationManager.ReceiveMessage(); + + if (string.Equals(MessageType.TestRunStatsChange, message.MessageType)) + { + var testRunChangedArgs = dataSerializer.DeserializePayload(message); + // Handle TestRunStatsChange here + } + else if (string.Equals(MessageType.ExecutionComplete, message.MessageType)) + { + var testRunCompletePayload = dataSerializer.DeserializePayload(message); + + // Handle TestRunComplete here + // Set the flag, to end the loop. + isTestRunComplete = true; + } + else if (string.Equals(MessageType.TestMessage, message.MessageType)) + { + var testMessagePayload = dataSerializer.DeserializePayload(message); + // Handle log messages here + } + } + } + } +} diff --git a/samples/Microsoft.TestPlatform.Protocol/Properties/AssemblyInfo.cs b/samples/Microsoft.TestPlatform.Protocol/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..ecbfce1cf0 --- /dev/null +++ b/samples/Microsoft.TestPlatform.Protocol/Properties/AssemblyInfo.cs @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Microsoft.TestPlatform.Protocol")] +[assembly: AssemblyTrademark("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("4ec14041-7804-4840-ae70-98babdc8b0e2")] diff --git a/samples/Microsoft.TestPlatform.Protocol/Properties/launchSettings.json b/samples/Microsoft.TestPlatform.Protocol/Properties/launchSettings.json new file mode 100644 index 0000000000..7521e2ddbe --- /dev/null +++ b/samples/Microsoft.TestPlatform.Protocol/Properties/launchSettings.json @@ -0,0 +1,7 @@ +{ + "profiles": { + "Microsoft.TestPlatform.Protocol": { + "commandName": "Project" + } + } +} \ No newline at end of file diff --git a/samples/Microsoft.TestPlatform.Protocol/RunnerProcessManager.cs b/samples/Microsoft.TestPlatform.Protocol/RunnerProcessManager.cs new file mode 100644 index 0000000000..f52f48f39e --- /dev/null +++ b/samples/Microsoft.TestPlatform.Protocol/RunnerProcessManager.cs @@ -0,0 +1,125 @@ +// 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.Protocol +{ + using System; + using System.Diagnostics; + using System.IO; + + /// + /// dotnet.exe process manager + /// + internal class RunnerProcessManager + { + private object syncObject = new object(); + + private bool vstestConsoleStarted = false; + + private bool vstestConsoleExited = false; + + private Process process; + + public event EventHandler ProcessExited; + + #region Constructor + + public RunnerProcessManager() + { + } + + #endregion Constructor + + public bool IsProcessInitialized() + { + lock (syncObject) + { + return this.vstestConsoleStarted && !vstestConsoleExited && + this.process != null; + } + } + + /// + /// Call dotnet.exe with the parameters previously specified + /// + public void StartProcess(string[] args) + { + this.process = new Process(); + process.StartInfo.FileName = GetDotnetHostFullPath(); + + if (args != null) + { + process.StartInfo.Arguments = args.Length < 2 ? args[0] : string.Join(" ", args); + } + process.StartInfo.Arguments = "vstest" + " " + process.StartInfo.Arguments; + + process.StartInfo.UseShellExecute = false; + process.StartInfo.CreateNoWindow = true; + + process.Start(); + process.EnableRaisingEvents = true; + process.Exited += Process_Exited; + + lock (syncObject) + { + vstestConsoleExited = false; + vstestConsoleStarted = true; + } + } + + public void ShutdownProcess() + { + // Ideally process should die by itself + if (IsProcessInitialized()) + { + this.process.Kill(); + this.process.Dispose(); + this.process = null; + } + } + + /// + /// Get full path for the .net host + /// + /// Full path to dotnet executable + /// Debuggers require the full path of executable to launch it. + private string GetDotnetHostFullPath() + { + char separator = ';'; + var dotnetExeName = "dotnet.exe"; + +#if !NET46 + // Use semicolon(;) as path separator for windows + // colon(:) for Linux and OSX + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + separator = ':'; + dotnetExeName = "dotnet"; + } +#endif + + var pathString = Environment.GetEnvironmentVariable("PATH"); + foreach (string path in pathString.Split(separator)) + { + string exeFullPath = Path.Combine(path.Trim(), dotnetExeName); + if (File.Exists(exeFullPath)) + { + return exeFullPath; + } + } + + string errorMessage = String.Format("Unable to find dotnet.exe"); + Console.WriteLine("Error : {0}", errorMessage); + throw new FileNotFoundException(errorMessage); + } + + private void Process_Exited(object sender, EventArgs e) + { + lock (syncObject) + { + vstestConsoleExited = true; + this.ProcessExited?.Invoke(sender, e); + } + } + } +} \ No newline at end of file diff --git a/samples/UnitTestProject/UnitTest.cs b/samples/UnitTestProject/UnitTest.cs index 9d8ee99724..c35757a177 100644 --- a/samples/UnitTestProject/UnitTest.cs +++ b/samples/UnitTestProject/UnitTest.cs @@ -8,9 +8,62 @@ namespace UnitTestProject [TestClass] public class UnitTest { + /// + /// The passing test. + /// [TestMethod] - public void TestMethod1() + public void PassingTest() { + Assert.AreEqual(2, 2); + } + + /// + /// The failing test. + /// + [TestMethod] + public void FailingTest() + { + Assert.AreEqual(2, 3); + } + + /// + /// The skipping test. + /// + [Ignore] + [TestMethod] + public void SkippingTest() + { + } + + [TestCategory("CategoryA")] + [TestMethod] + public void TestWithTestCategory() + { + Assert.AreEqual(1, 1); + } + + [Priority(0)] + [TestMethod] + public void TestWithPriority() + { + Assert.AreEqual(1, 1); + } + + [TestProperty("Property1", "Value1")] + [TestProperty("Property2", "Value2")] + [TestMethod] + public void TestWithProperties() + { + Assert.AreEqual(1, 1); + } + + [TestCategory("CategoryA")] + [Priority(1)] + [TestProperty("Property2", "Value2")] + [TestMethod] + public void FailingTestWithTraits() + { + Assert.Fail(); } } } diff --git a/samples/UnitTestProject/UnitTestProject.csproj b/samples/UnitTestProject/UnitTestProject.csproj index 9503396589..a8527f9017 100644 --- a/samples/UnitTestProject/UnitTestProject.csproj +++ b/samples/UnitTestProject/UnitTestProject.csproj @@ -1,8 +1,8 @@  - netcoreapp1.0;net46 + Exe UnitTestProject $(PackageTargetFallback);dnxcore50;portable-net45+win8 false @@ -14,12 +14,10 @@ false false - - + - 1.0.0-alpha-20161104-2 @@ -29,28 +27,30 @@ 1.6.0 - 1.0.4-preview + 1.0.8-rc + + + 1.1.8-rc - 1.0.0 - 15.0.0-preview-20161123-03 + 15.0.0-preview-20161216-01 - - + + + $(DefineConstants);RELEASE - - + \ No newline at end of file