diff --git a/Microsoft.DotNet.Cli.sln b/Microsoft.DotNet.Cli.sln index 203948f0c..7c895d033 100644 --- a/Microsoft.DotNet.Cli.sln +++ b/Microsoft.DotNet.Cli.sln @@ -22,8 +22,6 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.DotNet.Compiler.C EndProject Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.DotNet.ProjectModel.Workspaces", "src\Microsoft.DotNet.ProjectModel.Workspaces\Microsoft.DotNet.ProjectModel.Workspaces.xproj", "{BD7833F8-3209-4682-BF75-B4BCA883E279}" EndProject -Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.DotNet.Runtime", "src\Microsoft.DotNet.Runtime\Microsoft.DotNet.Runtime.xproj", "{DB29F219-DC92-4AF7-A2EE-E89FFBB3F5F0}" -EndProject Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.Extensions.Testing.Abstractions", "src\Microsoft.Extensions.Testing.Abstractions\Microsoft.Extensions.Testing.Abstractions.xproj", "{DCDFE282-03DE-4DBC-B90C-CC3CE3EC8162}" EndProject Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.DotNet.ProjectModel.Loader", "src\Microsoft.DotNet.ProjectModel.Loader\Microsoft.DotNet.ProjectModel.Loader.xproj", "{C7AF0290-EF0D-44DC-9EDC-600803B664F8}" @@ -64,14 +62,10 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.DotNet.Cli.Build. EndProject Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "dotnet-compile.UnitTests", "test\dotnet-compile.UnitTests\dotnet-compile.UnitTests.xproj", "{920B71D8-62DA-4F5E-8A26-926C113F1D97}" EndProject -Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "TestApp", "TestAssets\TestProjects\TestApp\TestApp.xproj", "{58808BBC-371E-47D6-A3D0-4902145EDA4E}" -EndProject Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "TestAppWithArgs", "TestAssets\TestProjects\TestAppWithArgs\TestAppWithArgs.xproj", "{DA8E0E9E-A6D6-4583-864C-8F40465E3A48}" EndProject Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "TestAppWithContents", "TestAssets\TestProjects\TestAppWithContents\TestAppWithContents.xproj", "{0138CB8F-4AA9-4029-A21E-C07C30F425BA}" EndProject -Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "TestLibrary", "TestAssets\TestProjects\TestLibrary\TestLibrary.xproj", "{947DD232-8D9B-4B78-9C6A-94F807D2DD58}" -EndProject Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "TestProjectToProjectDependencies", "TestAssets\TestProjects\TestProjectToProjectDependencies\TestProjectToProjectDependencies.xproj", "{947DD232-8D9B-4B78-9C6A-94F807D22222}" EndProject Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.DotNet.InternalAbstractions", "src\Microsoft.DotNet.InternalAbstractions\Microsoft.DotNet.InternalAbstractions.xproj", "{BD4F0750-4E81-4AD2-90B5-E470881792C3}" @@ -84,6 +78,8 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.DotNet.Cli.Msi.Te EndProject Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.Extensions.DependencyModel.Tests", "..\cli1\test\Microsoft.Extensions.DependencyModel.Tests\Microsoft.Extensions.DependencyModel.Tests.xproj", "{4A4711D8-4312-49FC-87B5-4F183F4C6A51}" EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "dotnet-test.UnitTests", "test\dotnet-test.UnitTests\dotnet-test.UnitTests.xproj", "{857274AC-E741-4266-A7FD-14DEE0C1CC96}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -160,22 +156,6 @@ Global {BD7833F8-3209-4682-BF75-B4BCA883E279}.RelWithDebInfo|Any CPU.Build.0 = Release|Any CPU {BD7833F8-3209-4682-BF75-B4BCA883E279}.RelWithDebInfo|x64.ActiveCfg = Release|Any CPU {BD7833F8-3209-4682-BF75-B4BCA883E279}.RelWithDebInfo|x64.Build.0 = Release|Any CPU - {DB29F219-DC92-4AF7-A2EE-E89FFBB3F5F0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {DB29F219-DC92-4AF7-A2EE-E89FFBB3F5F0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {DB29F219-DC92-4AF7-A2EE-E89FFBB3F5F0}.Debug|x64.ActiveCfg = Debug|Any CPU - {DB29F219-DC92-4AF7-A2EE-E89FFBB3F5F0}.Debug|x64.Build.0 = Debug|Any CPU - {DB29F219-DC92-4AF7-A2EE-E89FFBB3F5F0}.MinSizeRel|Any CPU.ActiveCfg = Debug|Any CPU - {DB29F219-DC92-4AF7-A2EE-E89FFBB3F5F0}.MinSizeRel|Any CPU.Build.0 = Debug|Any CPU - {DB29F219-DC92-4AF7-A2EE-E89FFBB3F5F0}.MinSizeRel|x64.ActiveCfg = Debug|Any CPU - {DB29F219-DC92-4AF7-A2EE-E89FFBB3F5F0}.MinSizeRel|x64.Build.0 = Debug|Any CPU - {DB29F219-DC92-4AF7-A2EE-E89FFBB3F5F0}.Release|Any CPU.ActiveCfg = Release|Any CPU - {DB29F219-DC92-4AF7-A2EE-E89FFBB3F5F0}.Release|Any CPU.Build.0 = Release|Any CPU - {DB29F219-DC92-4AF7-A2EE-E89FFBB3F5F0}.Release|x64.ActiveCfg = Release|Any CPU - {DB29F219-DC92-4AF7-A2EE-E89FFBB3F5F0}.Release|x64.Build.0 = Release|Any CPU - {DB29F219-DC92-4AF7-A2EE-E89FFBB3F5F0}.RelWithDebInfo|Any CPU.ActiveCfg = Release|Any CPU - {DB29F219-DC92-4AF7-A2EE-E89FFBB3F5F0}.RelWithDebInfo|Any CPU.Build.0 = Release|Any CPU - {DB29F219-DC92-4AF7-A2EE-E89FFBB3F5F0}.RelWithDebInfo|x64.ActiveCfg = Release|Any CPU - {DB29F219-DC92-4AF7-A2EE-E89FFBB3F5F0}.RelWithDebInfo|x64.Build.0 = Release|Any CPU {DCDFE282-03DE-4DBC-B90C-CC3CE3EC8162}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {DCDFE282-03DE-4DBC-B90C-CC3CE3EC8162}.Debug|Any CPU.Build.0 = Debug|Any CPU {DCDFE282-03DE-4DBC-B90C-CC3CE3EC8162}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -432,22 +412,6 @@ Global {920B71D8-62DA-4F5E-8A26-926C113F1D97}.RelWithDebInfo|Any CPU.Build.0 = Release|Any CPU {920B71D8-62DA-4F5E-8A26-926C113F1D97}.RelWithDebInfo|x64.ActiveCfg = Release|Any CPU {920B71D8-62DA-4F5E-8A26-926C113F1D97}.RelWithDebInfo|x64.Build.0 = Release|Any CPU - {58808BBC-371E-47D6-A3D0-4902145EDA4E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {58808BBC-371E-47D6-A3D0-4902145EDA4E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {58808BBC-371E-47D6-A3D0-4902145EDA4E}.Debug|x64.ActiveCfg = Debug|Any CPU - {58808BBC-371E-47D6-A3D0-4902145EDA4E}.Debug|x64.Build.0 = Debug|Any CPU - {58808BBC-371E-47D6-A3D0-4902145EDA4E}.MinSizeRel|Any CPU.ActiveCfg = Debug|Any CPU - {58808BBC-371E-47D6-A3D0-4902145EDA4E}.MinSizeRel|Any CPU.Build.0 = Debug|Any CPU - {58808BBC-371E-47D6-A3D0-4902145EDA4E}.MinSizeRel|x64.ActiveCfg = Debug|Any CPU - {58808BBC-371E-47D6-A3D0-4902145EDA4E}.MinSizeRel|x64.Build.0 = Debug|Any CPU - {58808BBC-371E-47D6-A3D0-4902145EDA4E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {58808BBC-371E-47D6-A3D0-4902145EDA4E}.Release|Any CPU.Build.0 = Release|Any CPU - {58808BBC-371E-47D6-A3D0-4902145EDA4E}.Release|x64.ActiveCfg = Release|Any CPU - {58808BBC-371E-47D6-A3D0-4902145EDA4E}.Release|x64.Build.0 = Release|Any CPU - {58808BBC-371E-47D6-A3D0-4902145EDA4E}.RelWithDebInfo|Any CPU.ActiveCfg = Release|Any CPU - {58808BBC-371E-47D6-A3D0-4902145EDA4E}.RelWithDebInfo|Any CPU.Build.0 = Release|Any CPU - {58808BBC-371E-47D6-A3D0-4902145EDA4E}.RelWithDebInfo|x64.ActiveCfg = Release|Any CPU - {58808BBC-371E-47D6-A3D0-4902145EDA4E}.RelWithDebInfo|x64.Build.0 = Release|Any CPU {DA8E0E9E-A6D6-4583-864C-8F40465E3A48}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {DA8E0E9E-A6D6-4583-864C-8F40465E3A48}.Debug|Any CPU.Build.0 = Debug|Any CPU {DA8E0E9E-A6D6-4583-864C-8F40465E3A48}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -480,22 +444,6 @@ Global {0138CB8F-4AA9-4029-A21E-C07C30F425BA}.RelWithDebInfo|Any CPU.Build.0 = Release|Any CPU {0138CB8F-4AA9-4029-A21E-C07C30F425BA}.RelWithDebInfo|x64.ActiveCfg = Release|Any CPU {0138CB8F-4AA9-4029-A21E-C07C30F425BA}.RelWithDebInfo|x64.Build.0 = Release|Any CPU - {947DD232-8D9B-4B78-9C6A-94F807D2DD58}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {947DD232-8D9B-4B78-9C6A-94F807D2DD58}.Debug|Any CPU.Build.0 = Debug|Any CPU - {947DD232-8D9B-4B78-9C6A-94F807D2DD58}.Debug|x64.ActiveCfg = Debug|Any CPU - {947DD232-8D9B-4B78-9C6A-94F807D2DD58}.Debug|x64.Build.0 = Debug|Any CPU - {947DD232-8D9B-4B78-9C6A-94F807D2DD58}.MinSizeRel|Any CPU.ActiveCfg = Debug|Any CPU - {947DD232-8D9B-4B78-9C6A-94F807D2DD58}.MinSizeRel|Any CPU.Build.0 = Debug|Any CPU - {947DD232-8D9B-4B78-9C6A-94F807D2DD58}.MinSizeRel|x64.ActiveCfg = Debug|Any CPU - {947DD232-8D9B-4B78-9C6A-94F807D2DD58}.MinSizeRel|x64.Build.0 = Debug|Any CPU - {947DD232-8D9B-4B78-9C6A-94F807D2DD58}.Release|Any CPU.ActiveCfg = Release|Any CPU - {947DD232-8D9B-4B78-9C6A-94F807D2DD58}.Release|Any CPU.Build.0 = Release|Any CPU - {947DD232-8D9B-4B78-9C6A-94F807D2DD58}.Release|x64.ActiveCfg = Release|Any CPU - {947DD232-8D9B-4B78-9C6A-94F807D2DD58}.Release|x64.Build.0 = Release|Any CPU - {947DD232-8D9B-4B78-9C6A-94F807D2DD58}.RelWithDebInfo|Any CPU.ActiveCfg = Release|Any CPU - {947DD232-8D9B-4B78-9C6A-94F807D2DD58}.RelWithDebInfo|Any CPU.Build.0 = Release|Any CPU - {947DD232-8D9B-4B78-9C6A-94F807D2DD58}.RelWithDebInfo|x64.ActiveCfg = Release|Any CPU - {947DD232-8D9B-4B78-9C6A-94F807D2DD58}.RelWithDebInfo|x64.Build.0 = Release|Any CPU {947DD232-8D9B-4B78-9C6A-94F807D22222}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {947DD232-8D9B-4B78-9C6A-94F807D22222}.Debug|Any CPU.Build.0 = Debug|Any CPU {947DD232-8D9B-4B78-9C6A-94F807D22222}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -560,6 +508,22 @@ Global {0B31C336-149D-471A-B7B1-27B0F1E80F83}.RelWithDebInfo|Any CPU.Build.0 = Release|Any CPU {0B31C336-149D-471A-B7B1-27B0F1E80F83}.RelWithDebInfo|x64.ActiveCfg = Release|Any CPU {0B31C336-149D-471A-B7B1-27B0F1E80F83}.RelWithDebInfo|x64.Build.0 = Release|Any CPU + {857274AC-E741-4266-A7FD-14DEE0C1CC96}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {857274AC-E741-4266-A7FD-14DEE0C1CC96}.Debug|Any CPU.Build.0 = Debug|Any CPU + {857274AC-E741-4266-A7FD-14DEE0C1CC96}.Debug|x64.ActiveCfg = Debug|Any CPU + {857274AC-E741-4266-A7FD-14DEE0C1CC96}.Debug|x64.Build.0 = Debug|Any CPU + {857274AC-E741-4266-A7FD-14DEE0C1CC96}.MinSizeRel|Any CPU.ActiveCfg = Debug|Any CPU + {857274AC-E741-4266-A7FD-14DEE0C1CC96}.MinSizeRel|Any CPU.Build.0 = Debug|Any CPU + {857274AC-E741-4266-A7FD-14DEE0C1CC96}.MinSizeRel|x64.ActiveCfg = Debug|Any CPU + {857274AC-E741-4266-A7FD-14DEE0C1CC96}.MinSizeRel|x64.Build.0 = Debug|Any CPU + {857274AC-E741-4266-A7FD-14DEE0C1CC96}.Release|Any CPU.ActiveCfg = Release|Any CPU + {857274AC-E741-4266-A7FD-14DEE0C1CC96}.Release|Any CPU.Build.0 = Release|Any CPU + {857274AC-E741-4266-A7FD-14DEE0C1CC96}.Release|x64.ActiveCfg = Release|Any CPU + {857274AC-E741-4266-A7FD-14DEE0C1CC96}.Release|x64.Build.0 = Release|Any CPU + {857274AC-E741-4266-A7FD-14DEE0C1CC96}.RelWithDebInfo|Any CPU.ActiveCfg = Release|Any CPU + {857274AC-E741-4266-A7FD-14DEE0C1CC96}.RelWithDebInfo|Any CPU.Build.0 = Release|Any CPU + {857274AC-E741-4266-A7FD-14DEE0C1CC96}.RelWithDebInfo|x64.ActiveCfg = Release|Any CPU + {857274AC-E741-4266-A7FD-14DEE0C1CC96}.RelWithDebInfo|x64.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -569,7 +533,6 @@ Global {303677D5-7312-4C3F-BAEE-BEB1A9BD9FE6} = {ED2FE3E2-F7E7-4389-8231-B65123F2076F} {A16958E1-24C7-4F1E-B317-204AD91625DD} = {ED2FE3E2-F7E7-4389-8231-B65123F2076F} {BD7833F8-3209-4682-BF75-B4BCA883E279} = {ED2FE3E2-F7E7-4389-8231-B65123F2076F} - {DB29F219-DC92-4AF7-A2EE-E89FFBB3F5F0} = {ED2FE3E2-F7E7-4389-8231-B65123F2076F} {DCDFE282-03DE-4DBC-B90C-CC3CE3EC8162} = {ED2FE3E2-F7E7-4389-8231-B65123F2076F} {C7AF0290-EF0D-44DC-9EDC-600803B664F8} = {ED2FE3E2-F7E7-4389-8231-B65123F2076F} {08A68C6A-86F6-4ED2-89A7-B166D33E9F85} = {0722D325-24C8-4E83-B5AF-0A083E7F0749} @@ -587,14 +550,14 @@ Global {D7B9695D-23EB-4EA8-B8AB-707A0092E1D5} = {88278B81-7649-45DC-8A6A-D3A645C5AFC3} {49BEB486-AB5A-4416-91EA-8CD34ABB0C9D} = {88278B81-7649-45DC-8A6A-D3A645C5AFC3} {920B71D8-62DA-4F5E-8A26-926C113F1D97} = {17735A9D-BFD9-4585-A7CB-3208CA6EA8A7} - {58808BBC-371E-47D6-A3D0-4902145EDA4E} = {713CBFBB-5392-438D-B766-A9A585EF1BB8} {DA8E0E9E-A6D6-4583-864C-8F40465E3A48} = {713CBFBB-5392-438D-B766-A9A585EF1BB8} {0138CB8F-4AA9-4029-A21E-C07C30F425BA} = {713CBFBB-5392-438D-B766-A9A585EF1BB8} - {947DD232-8D9B-4B78-9C6A-94F807D2DD58} = {713CBFBB-5392-438D-B766-A9A585EF1BB8} {947DD232-8D9B-4B78-9C6A-94F807D22222} = {713CBFBB-5392-438D-B766-A9A585EF1BB8} {BD4F0750-4E81-4AD2-90B5-E470881792C3} = {ED2FE3E2-F7E7-4389-8231-B65123F2076F} {4A4711D8-4312-49FC-87B5-4F183F4C6A51} = {17735A9D-BFD9-4585-A7CB-3208CA6EA8A7} + {0745410A-6629-47EB-AAB5-08D6288CAD72} = {17735A9D-BFD9-4585-A7CB-3208CA6EA8A7} {0E3300A4-DF54-40BF-87D8-E7658330C288} = {17735A9D-BFD9-4585-A7CB-3208CA6EA8A7} {0B31C336-149D-471A-B7B1-27B0F1E80F83} = {0E3300A4-DF54-40BF-87D8-E7658330C288} + {857274AC-E741-4266-A7FD-14DEE0C1CC96} = {17735A9D-BFD9-4585-A7CB-3208CA6EA8A7} EndGlobalSection EndGlobal diff --git a/global.json b/global.json index 6ea600c29..0018a7bf8 100644 --- a/global.json +++ b/global.json @@ -1,3 +1,6 @@ { - "projects": [ "src", "test" ] + "projects": [ "src", "test" ], + "sdk": { + "version": "1.0.0-rc2-16444" + } } diff --git a/src/dotnet/commands/dotnet-test/CommandTestRunnerExtensions.cs b/src/dotnet/commands/dotnet-test/CommandTestRunnerExtensions.cs new file mode 100644 index 000000000..3188c167f --- /dev/null +++ b/src/dotnet/commands/dotnet-test/CommandTestRunnerExtensions.cs @@ -0,0 +1,16 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Diagnostics; +using Microsoft.DotNet.Cli.Utils; + +namespace Microsoft.DotNet.Tools.Test +{ + public static class CommandTestRunnerExtensions + { + public static ProcessStartInfo ToProcessStartInfo(this ICommand command) + { + return new ProcessStartInfo(command.CommandName, command.CommandArgs); + } + } +} diff --git a/src/dotnet/commands/dotnet-test/DotnetTest.cs b/src/dotnet/commands/dotnet-test/DotnetTest.cs new file mode 100644 index 000000000..ab895d248 --- /dev/null +++ b/src/dotnet/commands/dotnet-test/DotnetTest.cs @@ -0,0 +1,106 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using Microsoft.Extensions.Testing.Abstractions; + +namespace Microsoft.DotNet.Tools.Test +{ + public class DotnetTest : IDotnetTest + { + private readonly IList _channels; + private readonly IList _messageHandlers; + private readonly ITestMessagesCollection _messages; + + public IDotnetTestMessageHandler TestSessionTerminateMessageHandler { private get; set; } + public IDotnetTestMessageHandler UnknownMessageHandler { private get; set; } + + public DotnetTestState State { get; private set; } + + public string PathToAssemblyUnderTest { get; } + + public DotnetTest(ITestMessagesCollection messages, string pathToAssemblyUnderTest) + { + PathToAssemblyUnderTest = pathToAssemblyUnderTest; + State = DotnetTestState.InitialState; + _channels = new List(); + _messageHandlers = new List(); + _messages = messages; + } + + public DotnetTest AddMessageHandler(IDotnetTestMessageHandler messageHandler) + { + _messageHandlers.Add(messageHandler); + + return this; + } + + public void StartHandlingMessages() + { + Message message; + while (_messages.TryTake(out message)) + { + HandleMessage(message); + } + } + + public void StartListeningTo(IReportingChannel reportingChannel) + { + ValidateSpecialMessageHandlersArePresent(); + + _channels.Add(reportingChannel); + reportingChannel.MessageReceived += OnMessageReceived; + } + + public void Dispose() + { + foreach (var reportingChannel in _channels) + { + reportingChannel.Dispose(); + } + } + + private void ValidateSpecialMessageHandlersArePresent() + { + if (TestSessionTerminateMessageHandler == null) + { + throw new InvalidOperationException("The TestSession.Terminate message handler needs to be set."); + } + + if (UnknownMessageHandler == null) + { + throw new InvalidOperationException("The unknown message handler needs to be set."); + } + } + + private void HandleMessage(Message message) + { + foreach (var messageHandler in _messageHandlers) + { + var nextState = messageHandler.HandleMessage(this, message); + + if (nextState != DotnetTestState.NoOp) + { + State = nextState; + return; + } + } + + UnknownMessageHandler.HandleMessage(this, message); + } + + private void OnMessageReceived(object sender, Message message) + { + if (!TerminateTestSession(message)) + { + _messages.Add(message); + } + } + + private bool TerminateTestSession(Message message) + { + return TestSessionTerminateMessageHandler.HandleMessage(this, message) == DotnetTestState.Terminated; + } + } +} diff --git a/src/dotnet/commands/dotnet-test/DotnetTestExtensions.cs b/src/dotnet/commands/dotnet-test/DotnetTestExtensions.cs new file mode 100644 index 000000000..9a173cb0a --- /dev/null +++ b/src/dotnet/commands/dotnet-test/DotnetTestExtensions.cs @@ -0,0 +1,61 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Microsoft.DotNet.Cli.Tools.Test; + +namespace Microsoft.DotNet.Tools.Test +{ + public static class DotnetTestExtensions + { + public static IDotnetTest AddNonSpecificMessageHandlers( + this IDotnetTest dotnetTest, + ITestMessagesCollection messages, + IReportingChannel adapterChannel) + { + dotnetTest.TestSessionTerminateMessageHandler = new TestSessionTerminateMessageHandler(messages); + dotnetTest.UnknownMessageHandler = new UnknownMessageHandler(adapterChannel); + + dotnetTest.AddMessageHandler(new VersionCheckMessageHandler(adapterChannel)); + + return dotnetTest; + } + + public static IDotnetTest AddTestDiscoveryMessageHandlers( + this IDotnetTest dotnetTest, + IReportingChannel adapterChannel, + IReportingChannelFactory reportingChannelFactory, + ITestRunnerFactory testRunnerFactory) + { + dotnetTest.AddMessageHandler( + new TestDiscoveryStartMessageHandler(testRunnerFactory, adapterChannel, reportingChannelFactory)); + + return dotnetTest; + } + + public static IDotnetTest AddTestRunMessageHandlers( + this IDotnetTest dotnetTest, + IReportingChannel adapterChannel, + IReportingChannelFactory reportingChannelFactory, + ITestRunnerFactory testRunnerFactory) + { + dotnetTest.AddMessageHandler(new GetTestRunnerProcessStartInfoMessageHandler( + testRunnerFactory, + adapterChannel, + reportingChannelFactory)); + + return dotnetTest; + } + + public static IDotnetTest AddTestRunnnersMessageHandlers( + this IDotnetTest dotnetTest, + IReportingChannel adapterChannel) + { + dotnetTest.AddMessageHandler(new TestRunnerTestStartedMessageHandler(adapterChannel)); + dotnetTest.AddMessageHandler(new TestRunnerTestResultMessageHandler(adapterChannel)); + dotnetTest.AddMessageHandler(new TestRunnerTestFoundMessageHandler(adapterChannel)); + dotnetTest.AddMessageHandler(new TestRunnerTestCompletedMessageHandler(adapterChannel)); + + return dotnetTest; + } + } +} diff --git a/src/dotnet/commands/dotnet-test/DotnetTestState.cs b/src/dotnet/commands/dotnet-test/DotnetTestState.cs new file mode 100644 index 000000000..7520f5c75 --- /dev/null +++ b/src/dotnet/commands/dotnet-test/DotnetTestState.cs @@ -0,0 +1,18 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Microsoft.DotNet.Tools.Test +{ + public enum DotnetTestState + { + NoOp, + InitialState, + VersionCheckCompleted, + TestDiscoveryStarted, + TestDiscoveryCompleted, + TestExecutionSentTestRunnerProcessStartInfo, + TestExecutionStarted, + TestExecutionCompleted, + Terminated + } +} diff --git a/src/dotnet/commands/dotnet-test/IDotnetTest.cs b/src/dotnet/commands/dotnet-test/IDotnetTest.cs new file mode 100644 index 000000000..3d0386b8a --- /dev/null +++ b/src/dotnet/commands/dotnet-test/IDotnetTest.cs @@ -0,0 +1,24 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; + +namespace Microsoft.DotNet.Tools.Test +{ + public interface IDotnetTest : IDisposable + { + string PathToAssemblyUnderTest { get; } + + DotnetTestState State { get; } + + DotnetTest AddMessageHandler(IDotnetTestMessageHandler messageHandler); + + IDotnetTestMessageHandler TestSessionTerminateMessageHandler { set; } + + IDotnetTestMessageHandler UnknownMessageHandler { set; } + + void StartHandlingMessages(); + + void StartListeningTo(IReportingChannel reportingChannel); + } +} diff --git a/src/dotnet/commands/dotnet-test/IReportingChannel.cs b/src/dotnet/commands/dotnet-test/IReportingChannel.cs new file mode 100644 index 000000000..64fa1d6f7 --- /dev/null +++ b/src/dotnet/commands/dotnet-test/IReportingChannel.cs @@ -0,0 +1,21 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using Microsoft.Extensions.Testing.Abstractions; + +namespace Microsoft.DotNet.Tools.Test +{ + public interface IReportingChannel : IDisposable + { + event EventHandler MessageReceived; + + int Port { get; } + + void Send(Message message); + + void SendError(string error); + + void SendError(Exception ex); + } +} diff --git a/src/dotnet/commands/dotnet-test/IReportingChannelFactory.cs b/src/dotnet/commands/dotnet-test/IReportingChannelFactory.cs new file mode 100644 index 000000000..a1e7274e1 --- /dev/null +++ b/src/dotnet/commands/dotnet-test/IReportingChannelFactory.cs @@ -0,0 +1,12 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Microsoft.DotNet.Tools.Test +{ + public interface IReportingChannelFactory + { + IReportingChannel CreateChannelWithAnyAvailablePort(); + + IReportingChannel CreateChannelWithPort(int port); + } +} diff --git a/src/dotnet/commands/dotnet-test/ITestMessagesCollection.cs b/src/dotnet/commands/dotnet-test/ITestMessagesCollection.cs new file mode 100644 index 000000000..ff462fb6a --- /dev/null +++ b/src/dotnet/commands/dotnet-test/ITestMessagesCollection.cs @@ -0,0 +1,17 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using Microsoft.Extensions.Testing.Abstractions; + +namespace Microsoft.DotNet.Tools.Test +{ + public interface ITestMessagesCollection : IDisposable + { + void Drain(); + + void Add(Message message); + + bool TryTake(out Message message); + } +} diff --git a/src/dotnet/commands/dotnet-test/MessageHandlers/GetTestRunnerProcessStartInfoMessageHandler.cs b/src/dotnet/commands/dotnet-test/MessageHandlers/GetTestRunnerProcessStartInfoMessageHandler.cs new file mode 100644 index 000000000..a95cc3a06 --- /dev/null +++ b/src/dotnet/commands/dotnet-test/MessageHandlers/GetTestRunnerProcessStartInfoMessageHandler.cs @@ -0,0 +1,62 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Microsoft.Extensions.Testing.Abstractions; +using Newtonsoft.Json.Linq; + +namespace Microsoft.DotNet.Tools.Test +{ + public class GetTestRunnerProcessStartInfoMessageHandler : IDotnetTestMessageHandler + { + private readonly ITestRunnerFactory _testRunnerFactory; + private readonly IReportingChannel _adapterChannel; + private readonly IReportingChannelFactory _reportingChannelFactory; + + public GetTestRunnerProcessStartInfoMessageHandler( + ITestRunnerFactory testRunnerFactory, + IReportingChannel adapterChannel, + IReportingChannelFactory reportingChannelFactory) + { + _testRunnerFactory = testRunnerFactory; + _adapterChannel = adapterChannel; + _reportingChannelFactory = reportingChannelFactory; + } + + public DotnetTestState HandleMessage(IDotnetTest dotnetTest, Message message) + { + var nextState = DotnetTestState.NoOp; + + if (CanHandleMessage(dotnetTest, message)) + { + DoHandleMessage(dotnetTest, message); + nextState = DotnetTestState.TestExecutionSentTestRunnerProcessStartInfo; + } + + return nextState; + } + + private void DoHandleMessage(IDotnetTest dotnetTest, Message message) + { + var testRunnerChannel = _reportingChannelFactory.CreateChannelWithAnyAvailablePort(); + + dotnetTest.StartListeningTo(testRunnerChannel); + + var testRunner = _testRunnerFactory.CreateTestRunner( + new RunTestsArgumentsBuilder(dotnetTest.PathToAssemblyUnderTest, testRunnerChannel.Port, message)); + + var processStartInfo = testRunner.GetProcessStartInfo(); + + _adapterChannel.Send(new Message + { + MessageType = TestMessageTypes.TestExecutionTestRunnerProcessStartInfo, + Payload = JToken.FromObject(processStartInfo) + }); + } + + private static bool CanHandleMessage(IDotnetTest dotnetTest, Message message) + { + return dotnetTest.State == DotnetTestState.VersionCheckCompleted && + message.MessageType == TestMessageTypes.TestExecutionGetTestRunnerProcessStartInfo; + } + } +} diff --git a/src/dotnet/commands/dotnet-test/MessageHandlers/IDotnetTestMessageHandler.cs b/src/dotnet/commands/dotnet-test/MessageHandlers/IDotnetTestMessageHandler.cs new file mode 100644 index 000000000..b2c44c614 --- /dev/null +++ b/src/dotnet/commands/dotnet-test/MessageHandlers/IDotnetTestMessageHandler.cs @@ -0,0 +1,12 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Microsoft.Extensions.Testing.Abstractions; + +namespace Microsoft.DotNet.Tools.Test +{ + public interface IDotnetTestMessageHandler + { + DotnetTestState HandleMessage(IDotnetTest dotnetTest, Message message); + } +} diff --git a/src/dotnet/commands/dotnet-test/MessageHandlers/TestDiscoveryStartMessageHandler.cs b/src/dotnet/commands/dotnet-test/MessageHandlers/TestDiscoveryStartMessageHandler.cs new file mode 100644 index 000000000..f4088f2d6 --- /dev/null +++ b/src/dotnet/commands/dotnet-test/MessageHandlers/TestDiscoveryStartMessageHandler.cs @@ -0,0 +1,75 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.DotNet.Tools.Test; +using Microsoft.Extensions.Testing.Abstractions; +using NuGet.Protocol.Core.v3; + +namespace Microsoft.DotNet.Cli.Tools.Test +{ + public class TestDiscoveryStartMessageHandler : IDotnetTestMessageHandler + { + private readonly ITestRunnerFactory _testRunnerFactory; + private readonly IReportingChannel _adapterChannel; + private readonly IReportingChannelFactory _reportingChannelFactory; + + public TestDiscoveryStartMessageHandler( + ITestRunnerFactory testRunnerFactory, + IReportingChannel adapterChannel, + IReportingChannelFactory reportingChannelFactory) + { + _testRunnerFactory = testRunnerFactory; + _adapterChannel = adapterChannel; + _reportingChannelFactory = reportingChannelFactory; + } + + public DotnetTestState HandleMessage(IDotnetTest dotnetTest, Message message) + { + var nextState = DotnetTestState.NoOp; + if (CanHandleMessage(dotnetTest, message)) + { + HandleMessage(dotnetTest); + nextState = DotnetTestState.TestDiscoveryStarted; + } + + return nextState; + } + + private void HandleMessage(IDotnetTest dotnetTest) + { + TestHostTracing.Source.TraceInformation("Starting Discovery"); + + DiscoverTests(dotnetTest); + } + + private void DiscoverTests(IDotnetTest dotnetTest) + { + var testRunnerResults = Enumerable.Empty(); + + try + { + var testRunnerChannel = _reportingChannelFactory.CreateChannelWithAnyAvailablePort(); + + dotnetTest.StartListeningTo(testRunnerChannel); + + var testRunner = _testRunnerFactory.CreateTestRunner( + new DiscoverTestsArgumentsBuilder(dotnetTest.PathToAssemblyUnderTest, testRunnerChannel.Port)); + + testRunner.RunTestCommand(); + } + catch (TestRunnerOperationFailedException e) + { + _adapterChannel.SendError(e.Message); + } + } + + private static bool CanHandleMessage(IDotnetTest dotnetTest, Message message) + { + return dotnetTest.State == DotnetTestState.VersionCheckCompleted && + message.MessageType == TestMessageTypes.TestDiscoveryStart; + } + } +} diff --git a/src/dotnet/commands/dotnet-test/MessageHandlers/TestMessageTypes.cs b/src/dotnet/commands/dotnet-test/MessageHandlers/TestMessageTypes.cs new file mode 100644 index 000000000..1cf24c941 --- /dev/null +++ b/src/dotnet/commands/dotnet-test/MessageHandlers/TestMessageTypes.cs @@ -0,0 +1,23 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Microsoft.DotNet.Tools.Test +{ + public static class TestMessageTypes + { + public const string TestRunnerTestResult = "TestExecution.TestResult"; + public const string TestRunnerTestStarted = "TestExecution.TestStarted"; + public const string TestRunnerTestCompleted = "TestRunner.TestCompleted"; + public const string TestRunnerTestFound = "TestDiscovery.TestFound"; + public const string TestSessionTerminate = "TestSession.Terminate"; + public const string VersionCheck = "ProtocolVersion"; + public const string TestDiscoveryStart = "TestDiscovery.Start"; + public const string TestDiscoveryCompleted = "TestDiscovery.Completed"; + public const string TestDiscoveryTestFound = "TestDiscovery.TestFound"; + public const string TestExecutionGetTestRunnerProcessStartInfo = "TestExecution.GetTestRunnerProcessStartInfo"; + public const string TestExecutionTestRunnerProcessStartInfo = "TestExecution.TestRunnerProcessStartInfo"; + public const string TestExecutionStarted = "TestExecution.TestStarted"; + public const string TestExecutionTestResult = "TestExecution.TestResult"; + public const string TestExecutionCompleted = "TestExecution.Completed"; + } +} diff --git a/src/dotnet/commands/dotnet-test/MessageHandlers/TestRunnerResultMessageHandler.cs b/src/dotnet/commands/dotnet-test/MessageHandlers/TestRunnerResultMessageHandler.cs new file mode 100644 index 000000000..e33a4bbfb --- /dev/null +++ b/src/dotnet/commands/dotnet-test/MessageHandlers/TestRunnerResultMessageHandler.cs @@ -0,0 +1,47 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Microsoft.Extensions.Testing.Abstractions; + +namespace Microsoft.DotNet.Tools.Test +{ + public abstract class TestRunnerResultMessageHandler : IDotnetTestMessageHandler + { + private readonly IReportingChannel _adapterChannel; + private readonly DotnetTestState _nextStateIfHandled; + private readonly string _messageIfHandled; + + protected TestRunnerResultMessageHandler( + IReportingChannel adapterChannel, + DotnetTestState nextStateIfHandled, + string messageIfHandled) + { + _adapterChannel = adapterChannel; + _nextStateIfHandled = nextStateIfHandled; + _messageIfHandled = messageIfHandled; + } + + public DotnetTestState HandleMessage(IDotnetTest dotnetTest, Message message) + { + var nextState = DotnetTestState.NoOp; + if (CanHandleMessage(dotnetTest, message)) + { + HandleMessage(message); + nextState = _nextStateIfHandled; + } + + return nextState; + } + + private void HandleMessage(Message message) + { + _adapterChannel.Send(new Message + { + MessageType = _messageIfHandled, + Payload = message.Payload + }); + } + + protected abstract bool CanHandleMessage(IDotnetTest dotnetTest, Message message); + } +} diff --git a/src/dotnet/commands/dotnet-test/MessageHandlers/TestRunnerTestCompletedMessageHandler.cs b/src/dotnet/commands/dotnet-test/MessageHandlers/TestRunnerTestCompletedMessageHandler.cs new file mode 100644 index 000000000..8e60b2838 --- /dev/null +++ b/src/dotnet/commands/dotnet-test/MessageHandlers/TestRunnerTestCompletedMessageHandler.cs @@ -0,0 +1,67 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Microsoft.Extensions.Testing.Abstractions; + +namespace Microsoft.DotNet.Tools.Test +{ + public class TestRunnerTestCompletedMessageHandler : IDotnetTestMessageHandler + { + private readonly IReportingChannel _adapterChannel; + + public TestRunnerTestCompletedMessageHandler(IReportingChannel adapterChannel) + { + _adapterChannel = adapterChannel; + } + + public DotnetTestState HandleMessage(IDotnetTest dotnetTest, Message message) + { + var nextState = DotnetTestState.NoOp; + if (CanHandleMessage(dotnetTest, message)) + { + DoHandleMessage(dotnetTest, message); + nextState = NextState(dotnetTest); + } + + return nextState; + } + + private void DoHandleMessage(IDotnetTest dotnetTest, Message message) + { + _adapterChannel.Send(new Message + { + MessageType = MessageType(dotnetTest) + }); + } + + private string MessageType(IDotnetTest dotnetTest) + { + return dotnetTest.State == DotnetTestState.TestDiscoveryStarted + ? TestMessageTypes.TestDiscoveryCompleted + : TestMessageTypes.TestExecutionCompleted; + } + + private DotnetTestState NextState(IDotnetTest dotnetTest) + { + return dotnetTest.State == DotnetTestState.TestDiscoveryStarted + ? DotnetTestState.TestDiscoveryCompleted + : DotnetTestState.TestExecutionCompleted; + } + + private bool CanHandleMessage(IDotnetTest dotnetTest, Message message) + { + return IsAtAnAcceptableState(dotnetTest) && CanAcceptMessage(message); + } + + private static bool CanAcceptMessage(Message message) + { + return message.MessageType == TestMessageTypes.TestRunnerTestCompleted; + } + + private static bool IsAtAnAcceptableState(IDotnetTest dotnetTest) + { + return (dotnetTest.State == DotnetTestState.TestDiscoveryStarted || + dotnetTest.State == DotnetTestState.TestExecutionStarted); + } + } +} diff --git a/src/dotnet/commands/dotnet-test/MessageHandlers/TestRunnerTestFoundMessageHandler.cs b/src/dotnet/commands/dotnet-test/MessageHandlers/TestRunnerTestFoundMessageHandler.cs new file mode 100644 index 000000000..78bbbba6a --- /dev/null +++ b/src/dotnet/commands/dotnet-test/MessageHandlers/TestRunnerTestFoundMessageHandler.cs @@ -0,0 +1,21 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Microsoft.Extensions.Testing.Abstractions; + +namespace Microsoft.DotNet.Tools.Test +{ + public class TestRunnerTestFoundMessageHandler : TestRunnerResultMessageHandler + { + public TestRunnerTestFoundMessageHandler(IReportingChannel adapterChannel) + : base(adapterChannel, DotnetTestState.TestDiscoveryStarted, TestMessageTypes.TestDiscoveryTestFound) + { + } + + protected override bool CanHandleMessage(IDotnetTest dotnetTest, Message message) + { + return dotnetTest.State == DotnetTestState.TestDiscoveryStarted && + message.MessageType == TestMessageTypes.TestRunnerTestFound; + } + } +} diff --git a/src/dotnet/commands/dotnet-test/MessageHandlers/TestRunnerTestResultMessageHandler.cs b/src/dotnet/commands/dotnet-test/MessageHandlers/TestRunnerTestResultMessageHandler.cs new file mode 100644 index 000000000..78bcb4a32 --- /dev/null +++ b/src/dotnet/commands/dotnet-test/MessageHandlers/TestRunnerTestResultMessageHandler.cs @@ -0,0 +1,21 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Microsoft.Extensions.Testing.Abstractions; + +namespace Microsoft.DotNet.Tools.Test +{ + public class TestRunnerTestResultMessageHandler : TestRunnerResultMessageHandler + { + public TestRunnerTestResultMessageHandler(IReportingChannel adapterChannel) + : base(adapterChannel, DotnetTestState.TestExecutionStarted, TestMessageTypes.TestExecutionTestResult) + { + } + + protected override bool CanHandleMessage(IDotnetTest dotnetTest, Message message) + { + return dotnetTest.State == DotnetTestState.TestExecutionStarted && + message.MessageType == TestMessageTypes.TestRunnerTestResult; + } + } +} diff --git a/src/dotnet/commands/dotnet-test/MessageHandlers/TestRunnerTestStartedMessageHandler.cs b/src/dotnet/commands/dotnet-test/MessageHandlers/TestRunnerTestStartedMessageHandler.cs new file mode 100644 index 000000000..c1271fc8e --- /dev/null +++ b/src/dotnet/commands/dotnet-test/MessageHandlers/TestRunnerTestStartedMessageHandler.cs @@ -0,0 +1,21 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Microsoft.Extensions.Testing.Abstractions; + +namespace Microsoft.DotNet.Tools.Test +{ + public class TestRunnerTestStartedMessageHandler : TestRunnerResultMessageHandler + { + public TestRunnerTestStartedMessageHandler(IReportingChannel adapterChannel) + : base(adapterChannel, DotnetTestState.TestExecutionStarted, TestMessageTypes.TestExecutionStarted) + { + } + + protected override bool CanHandleMessage(IDotnetTest dotnetTest, Message message) + { + return dotnetTest.State == DotnetTestState.TestExecutionSentTestRunnerProcessStartInfo && + message.MessageType == TestMessageTypes.TestRunnerTestStarted; + } + } +} diff --git a/src/dotnet/commands/dotnet-test/MessageHandlers/TestSessionTerminateMessageHandler.cs b/src/dotnet/commands/dotnet-test/MessageHandlers/TestSessionTerminateMessageHandler.cs new file mode 100644 index 000000000..39d1dd381 --- /dev/null +++ b/src/dotnet/commands/dotnet-test/MessageHandlers/TestSessionTerminateMessageHandler.cs @@ -0,0 +1,30 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Microsoft.Extensions.Testing.Abstractions; + +namespace Microsoft.DotNet.Tools.Test +{ + public class TestSessionTerminateMessageHandler : IDotnetTestMessageHandler + { + private readonly ITestMessagesCollection _messages; + + public TestSessionTerminateMessageHandler(ITestMessagesCollection messages) + { + _messages = messages; + } + + public DotnetTestState HandleMessage(IDotnetTest dotnetTest, Message message) + { + var nextState = DotnetTestState.NoOp; + + if (TestMessageTypes.TestSessionTerminate.Equals(message.MessageType)) + { + nextState = DotnetTestState.Terminated; + _messages.Drain(); + } + + return nextState; + } + } +} diff --git a/src/dotnet/commands/dotnet-test/MessageHandlers/UnknownMessageHandler.cs b/src/dotnet/commands/dotnet-test/MessageHandlers/UnknownMessageHandler.cs new file mode 100644 index 000000000..d0ce66db1 --- /dev/null +++ b/src/dotnet/commands/dotnet-test/MessageHandlers/UnknownMessageHandler.cs @@ -0,0 +1,30 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Diagnostics; +using Microsoft.Extensions.Testing.Abstractions; + +namespace Microsoft.DotNet.Tools.Test +{ + public class UnknownMessageHandler : IDotnetTestMessageHandler + { + private readonly IReportingChannel _adapterChannel; + + public UnknownMessageHandler(IReportingChannel adapterChannel) + { + _adapterChannel = adapterChannel; + } + + public DotnetTestState HandleMessage(IDotnetTest dotnetTest, Message message) + { + var error = $"No handler for message '{message.MessageType}' when at state '{dotnetTest.State}'"; + + TestHostTracing.Source.TraceEvent(TraceEventType.Error, 0, error); + + _adapterChannel.SendError(error); + + throw new InvalidOperationException(error); + } + } +} diff --git a/src/dotnet/commands/dotnet-test/MessageHandlers/VersionCheckMessageHandler.cs b/src/dotnet/commands/dotnet-test/MessageHandlers/VersionCheckMessageHandler.cs new file mode 100644 index 000000000..e6d458337 --- /dev/null +++ b/src/dotnet/commands/dotnet-test/MessageHandlers/VersionCheckMessageHandler.cs @@ -0,0 +1,56 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Microsoft.Extensions.Testing.Abstractions; +using Newtonsoft.Json.Linq; + +namespace Microsoft.DotNet.Tools.Test +{ + public class VersionCheckMessageHandler : IDotnetTestMessageHandler + { + private const int SupportedVersion = 1; + + private readonly IReportingChannel _adapterChannel; + + public VersionCheckMessageHandler(IReportingChannel adapterChannel) + { + _adapterChannel = adapterChannel; + } + + public DotnetTestState HandleMessage(IDotnetTest dotnetTest, Message message) + { + var nextState = DotnetTestState.NoOp; + if (CanHandleMessage(dotnetTest, message)) + { + HandleMessage(message); + nextState = DotnetTestState.VersionCheckCompleted; + } + + return nextState; + } + + private void HandleMessage(Message message) + { + var version = message.Payload?.ToObject().Version; + TestHostTracing.Source.TraceInformation( + "[ReportingChannel]: Requested Version: {0} - Using Version: {1}", + version, + SupportedVersion); + + _adapterChannel.Send(new Message + { + MessageType = TestMessageTypes.VersionCheck, + Payload = JToken.FromObject(new ProtocolVersionMessage + { + Version = SupportedVersion, + }), + }); + } + + private static bool CanHandleMessage(IDotnetTest dotnetTest, Message message) + { + return dotnetTest.State == DotnetTestState.InitialState && + TestMessageTypes.VersionCheck.Equals(message.MessageType); + } + } +} diff --git a/src/dotnet/commands/dotnet-test/Program.cs b/src/dotnet/commands/dotnet-test/Program.cs index 421626988..9168314e2 100644 --- a/src/dotnet/commands/dotnet-test/Program.cs +++ b/src/dotnet/commands/dotnet-test/Program.cs @@ -9,10 +9,6 @@ using System.Linq; using Microsoft.DotNet.Cli.Utils; using Microsoft.Dnx.Runtime.Common.CommandLine; using Microsoft.DotNet.ProjectModel; -using Microsoft.Extensions.Testing.Abstractions; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using NuGet.Frameworks; namespace Microsoft.DotNet.Tools.Test { @@ -58,7 +54,7 @@ namespace Microsoft.DotNet.Tools.Test var projectContext = projectContexts.First(); var testRunner = projectContext.ProjectFile.TestRunner; - + var configuration = configurationOption.Value() ?? Constants.DefaultConfiguration; if (portOption.HasValue()) @@ -105,154 +101,52 @@ namespace Microsoft.DotNet.Tools.Test .ExitCode; } - private static int RunDesignTime(int port, ProjectContext projectContext, string testRunner, string configuration) + private static int RunDesignTime( + int port, + ProjectContext + projectContext, + string testRunner, + string configuration) { Console.WriteLine("Listening on port {0}", port); - using (var channel = ReportingChannel.ListenOn(port)) - { - Console.WriteLine("Client accepted {0}", channel.Socket.LocalEndPoint); - HandleDesignTimeMessages(projectContext, testRunner, channel, configuration); + HandleDesignTimeMessages(projectContext, testRunner, port, configuration); - return 0; - } + return 0; } - private static void HandleDesignTimeMessages(ProjectContext projectContext, string testRunner, ReportingChannel channel, string configuration) + private static void HandleDesignTimeMessages( + ProjectContext projectContext, + string testRunner, + int port, + string configuration) { + var reportingChannelFactory = new ReportingChannelFactory(); + var adapterChannel = reportingChannelFactory.CreateChannelWithPort(port); + try { - var message = channel.ReadQueue.Take(); + var assemblyUnderTest = projectContext.GetOutputPaths(configuration).CompilationFiles.Assembly; + var messages = new TestMessagesCollection(); + using (var dotnetTest = new DotnetTest(messages, assemblyUnderTest)) + { + var commandFactory = new DotNetCommandFactory(); + var testRunnerFactory = new TestRunnerFactory(GetCommandName(testRunner), commandFactory); - if (message.MessageType == "ProtocolVersion") - { - HandleProtocolVersionMessage(message, channel); + dotnetTest + .AddNonSpecificMessageHandlers(messages, adapterChannel) + .AddTestDiscoveryMessageHandlers(adapterChannel, reportingChannelFactory, testRunnerFactory) + .AddTestRunMessageHandlers(adapterChannel, reportingChannelFactory, testRunnerFactory) + .AddTestRunnnersMessageHandlers(adapterChannel); - // Take the next message, which should be the command to execute. - message = channel.ReadQueue.Take(); - } + dotnetTest.StartListeningTo(adapterChannel); - if (message.MessageType == "TestDiscovery.Start") - { - HandleTestDiscoveryStartMessage(testRunner, channel, projectContext, configuration); - } - else if (message.MessageType == "TestExecution.Start") - { - HandleTestExecutionStartMessage(testRunner, message, channel, projectContext, configuration); - } - else - { - HandleUnknownMessage(message, channel); + dotnetTest.StartHandlingMessages(); } } catch (Exception ex) { - channel.SendError(ex); - } - } - - private static void HandleProtocolVersionMessage(Message message, ReportingChannel channel) - { - var version = message.Payload?.ToObject().Version; - var supportedVersion = 1; - TestHostTracing.Source.TraceInformation( - "[ReportingChannel]: Requested Version: {0} - Using Version: {1}", - version, - supportedVersion); - - channel.Send(new Message() - { - MessageType = "ProtocolVersion", - Payload = JToken.FromObject(new ProtocolVersionMessage() - { - Version = supportedVersion, - }), - }); - } - - private static void HandleTestDiscoveryStartMessage(string testRunner, ReportingChannel channel, ProjectContext projectContext, string configuration) - { - TestHostTracing.Source.TraceInformation("Starting Discovery"); - - var commandArgs = new List { projectContext.GetOutputPaths(configuration).CompilationFiles.Assembly }; - - commandArgs.AddRange(new[] - { - "--list", - "--designtime" - }); - - ExecuteRunnerCommand(testRunner, channel, commandArgs); - - channel.Send(new Message() - { - MessageType = "TestDiscovery.Response", - }); - - TestHostTracing.Source.TraceInformation("Completed Discovery"); - } - - private static void HandleTestExecutionStartMessage(string testRunner, Message message, ReportingChannel channel, ProjectContext projectContext, string configuration) - { - TestHostTracing.Source.TraceInformation("Starting Execution"); - - var commandArgs = new List { projectContext.GetOutputPaths(configuration).CompilationFiles.Assembly }; - - commandArgs.AddRange(new[] - { - "--designtime" - }); - - var tests = message.Payload?.ToObject().Tests; - if (tests != null) - { - foreach (var test in tests) - { - commandArgs.Add("--test"); - commandArgs.Add(test); - } - } - - ExecuteRunnerCommand(testRunner, channel, commandArgs); - - channel.Send(new Message() - { - MessageType = "TestExecution.Response", - }); - - TestHostTracing.Source.TraceInformation("Completed Execution"); - } - - private static void HandleUnknownMessage(Message message, ReportingChannel channel) - { - var error = string.Format("Unexpected message type: '{0}'.", message.MessageType); - - TestHostTracing.Source.TraceEvent(TraceEventType.Error, 0, error); - - channel.SendError(error); - - throw new InvalidOperationException(error); - } - - private static void ExecuteRunnerCommand(string testRunner, ReportingChannel channel, List commandArgs) - { - var result = Command.CreateDotNet(GetCommandName(testRunner), commandArgs, new NuGetFramework("DNXCore", Version.Parse("5.0"))) - .OnOutputLine(line => - { - try - { - channel.Send(JsonConvert.DeserializeObject(line)); - } - catch - { - TestHostTracing.Source.TraceInformation(line); - } - }) - .Execute(); - - if (result.ExitCode != 0) - { - channel.SendError($"{GetCommandName(testRunner)} returned '{result.ExitCode}'."); + adapterChannel.SendError(ex); } } diff --git a/src/dotnet/commands/dotnet-test/ReportingChannel.cs b/src/dotnet/commands/dotnet-test/ReportingChannel.cs index cf2f2d33c..b1663c994 100644 --- a/src/dotnet/commands/dotnet-test/ReportingChannel.cs +++ b/src/dotnet/commands/dotnet-test/ReportingChannel.cs @@ -2,7 +2,6 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; -using System.Collections.Concurrent; using System.Diagnostics; using System.IO; using System.Net; @@ -14,7 +13,7 @@ using Newtonsoft.Json.Linq; namespace Microsoft.DotNet.Tools.Test { - public class ReportingChannel : IDisposable + public class ReportingChannel : IReportingChannel { public static ReportingChannel ListenOn(int port) { @@ -32,7 +31,6 @@ namespace Microsoft.DotNet.Tools.Test private readonly BinaryWriter _writer; private readonly BinaryReader _reader; - private readonly ManualResetEventSlim _ackWaitHandle; private ReportingChannel(Socket socket) { @@ -41,18 +39,17 @@ namespace Microsoft.DotNet.Tools.Test var stream = new NetworkStream(Socket); _writer = new BinaryWriter(stream); _reader = new BinaryReader(stream); - _ackWaitHandle = new ManualResetEventSlim(); - - ReadQueue = new BlockingCollection(boundedCapacity: 1); // Read incoming messages on the background thread new Thread(ReadMessages) { IsBackground = true }.Start(); } - public BlockingCollection ReadQueue { get; } + public event EventHandler MessageReceived; public Socket Socket { get; private set; } + public int Port => ((IPEndPoint) Socket.LocalEndPoint).Port; + public void Send(Message message) { lock (_writer) @@ -62,7 +59,7 @@ namespace Microsoft.DotNet.Tools.Test TestHostTracing.Source.TraceEvent( TraceEventType.Verbose, 0, - "[ReportingChannel]: Send({0})", + "[ReportingChannel]: Send({0})", message); _writer.Write(JsonConvert.SerializeObject(message)); @@ -103,14 +100,8 @@ namespace Microsoft.DotNet.Tools.Test try { var message = JsonConvert.DeserializeObject(_reader.ReadString()); - ReadQueue.Add(message); - if (string.Equals(message.MessageType, "TestHost.Acknowledge")) - { - _ackWaitHandle.Set(); - ReadQueue.CompleteAdding(); - break; - } + MessageReceived?.Invoke(this, message); } catch (Exception ex) { @@ -126,24 +117,6 @@ namespace Microsoft.DotNet.Tools.Test public void Dispose() { - // Wait for a graceful disconnect - drain the queue until we get an 'ACK' - Message message; - while (ReadQueue.TryTake(out message, millisecondsTimeout: 1)) - { - } - - if (_ackWaitHandle.Wait(TimeSpan.FromSeconds(10))) - { - TestHostTracing.Source.TraceInformation("[ReportingChannel]: Received for ack from test host"); - } - else - { - TestHostTracing.Source.TraceEvent( - TraceEventType.Error, - 0, - "[ReportingChannel]: Timed out waiting for ack from test host"); - } - Socket.Dispose(); } } diff --git a/src/dotnet/commands/dotnet-test/ReportingChannelFactory.cs b/src/dotnet/commands/dotnet-test/ReportingChannelFactory.cs new file mode 100644 index 000000000..0dac24eba --- /dev/null +++ b/src/dotnet/commands/dotnet-test/ReportingChannelFactory.cs @@ -0,0 +1,18 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Microsoft.DotNet.Tools.Test +{ + public class ReportingChannelFactory : IReportingChannelFactory + { + public IReportingChannel CreateChannelWithAnyAvailablePort() + { + return ReportingChannel.ListenOn(0); + } + + public IReportingChannel CreateChannelWithPort(int port) + { + return ReportingChannel.ListenOn(port); + } + } +} diff --git a/src/dotnet/commands/dotnet-test/TestMessagesCollection.cs b/src/dotnet/commands/dotnet-test/TestMessagesCollection.cs new file mode 100644 index 000000000..a34815f99 --- /dev/null +++ b/src/dotnet/commands/dotnet-test/TestMessagesCollection.cs @@ -0,0 +1,73 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Concurrent; +using System.Diagnostics; +using System.Threading; +using Microsoft.Extensions.Testing.Abstractions; +using System; + +namespace Microsoft.DotNet.Tools.Test +{ + public class TestMessagesCollection : ITestMessagesCollection + { + private readonly ManualResetEventSlim _terminateWaitHandle; + private readonly BlockingCollection _readQueue; + + public TestMessagesCollection() + { + _readQueue = new BlockingCollection(boundedCapacity: 1); + _terminateWaitHandle = new ManualResetEventSlim(); + } + + public void Drain() + { + _terminateWaitHandle.Set(); + _readQueue.CompleteAdding(); + DrainQueue(); + } + + public void Add(Message message) + { + _readQueue.Add(message); + } + + public bool TryTake(out Message message) + { + message = null; + try + { + message = _readQueue.Take(); + } + catch (InvalidOperationException) + { + return false; + } + + return true; + } + + public void Dispose() + { + if (_terminateWaitHandle.Wait(TimeSpan.FromSeconds(10))) + { + TestHostTracing.Source.TraceInformation("[ReportingChannel]: Received TestSession:Terminate from test host"); + } + else + { + TestHostTracing.Source.TraceEvent( + TraceEventType.Error, + 0, + "[ReportingChannel]: Timed out waiting for aTestSession:Terminate from test host"); + } + } + + private void DrainQueue() + { + Message message; + while (_readQueue.TryTake(out message, millisecondsTimeout: 1)) + { + } + } + } +} diff --git a/src/dotnet/commands/dotnet-test/TestRunners/DiscoverTestsArgumentsBuilder.cs b/src/dotnet/commands/dotnet-test/TestRunners/DiscoverTestsArgumentsBuilder.cs new file mode 100644 index 000000000..7f1202d00 --- /dev/null +++ b/src/dotnet/commands/dotnet-test/TestRunners/DiscoverTestsArgumentsBuilder.cs @@ -0,0 +1,34 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Microsoft.Extensions.Testing.Abstractions; +using System.Collections.Generic; + +namespace Microsoft.DotNet.Tools.Test +{ + public class DiscoverTestsArgumentsBuilder : ITestRunnerArgumentsBuilder + { + private readonly string _assemblyUnderTest; + private readonly int _port; + + public DiscoverTestsArgumentsBuilder(string assemblyUnderTest, int port) + { + _assemblyUnderTest = assemblyUnderTest; + _port = port; + } + + public IEnumerable BuildArguments() + { + var commandArgs = new List + { + _assemblyUnderTest, + "--list", + "--designtime", + "--port", + $"{_port}" + }; + + return commandArgs; + } + } +} diff --git a/src/dotnet/commands/dotnet-test/TestRunners/ITestRunner.cs b/src/dotnet/commands/dotnet-test/TestRunners/ITestRunner.cs new file mode 100644 index 000000000..bdac41583 --- /dev/null +++ b/src/dotnet/commands/dotnet-test/TestRunners/ITestRunner.cs @@ -0,0 +1,16 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Generic; +using System.Diagnostics; +using Microsoft.Extensions.Testing.Abstractions; + +namespace Microsoft.DotNet.Tools.Test +{ + public interface ITestRunner + { + void RunTestCommand(); + + ProcessStartInfo GetProcessStartInfo(); + } +} diff --git a/src/dotnet/commands/dotnet-test/TestRunners/ITestRunnerArgumentsBuilder.cs b/src/dotnet/commands/dotnet-test/TestRunners/ITestRunnerArgumentsBuilder.cs new file mode 100644 index 000000000..55bbd3aa9 --- /dev/null +++ b/src/dotnet/commands/dotnet-test/TestRunners/ITestRunnerArgumentsBuilder.cs @@ -0,0 +1,12 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Generic; + +namespace Microsoft.DotNet.Tools.Test +{ + public interface ITestRunnerArgumentsBuilder + { + IEnumerable BuildArguments(); + } +} diff --git a/src/dotnet/commands/dotnet-test/TestRunners/ITestRunnerFactory.cs b/src/dotnet/commands/dotnet-test/TestRunners/ITestRunnerFactory.cs new file mode 100644 index 000000000..d702eccb7 --- /dev/null +++ b/src/dotnet/commands/dotnet-test/TestRunners/ITestRunnerFactory.cs @@ -0,0 +1,12 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Microsoft.Extensions.Testing.Abstractions; + +namespace Microsoft.DotNet.Tools.Test +{ + public interface ITestRunnerFactory + { + ITestRunner CreateTestRunner(ITestRunnerArgumentsBuilder argumentsBuilder); + } +} diff --git a/src/dotnet/commands/dotnet-test/TestRunners/RunTestsArgumentsBuilder.cs b/src/dotnet/commands/dotnet-test/TestRunners/RunTestsArgumentsBuilder.cs new file mode 100644 index 000000000..4397731f4 --- /dev/null +++ b/src/dotnet/commands/dotnet-test/TestRunners/RunTestsArgumentsBuilder.cs @@ -0,0 +1,45 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Generic; +using Microsoft.Extensions.Testing.Abstractions; + +namespace Microsoft.DotNet.Tools.Test +{ + public class RunTestsArgumentsBuilder : ITestRunnerArgumentsBuilder + { + private readonly string _assemblyUnderTest; + private readonly int _port; + private readonly Message _message; + + public RunTestsArgumentsBuilder(string assemblyUnderTest, int port, Message message) + { + _assemblyUnderTest = assemblyUnderTest; + _port = port; + _message = message; + } + + public IEnumerable BuildArguments() + { + var commandArgs = new List + { + _assemblyUnderTest, + "--designtime", + "--port", + $"{_port}" + }; + + var tests = _message.Payload?.ToObject().Tests; + if (tests != null) + { + foreach (var test in tests) + { + commandArgs.Add("--test"); + commandArgs.Add(test); + } + } + + return commandArgs; + } + } +} diff --git a/src/dotnet/commands/dotnet-test/TestRunners/TestRunner.cs b/src/dotnet/commands/dotnet-test/TestRunners/TestRunner.cs new file mode 100644 index 000000000..7a3a14aed --- /dev/null +++ b/src/dotnet/commands/dotnet-test/TestRunners/TestRunner.cs @@ -0,0 +1,59 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Diagnostics; +using Microsoft.DotNet.Cli.Utils; +using NuGet.Frameworks; + +namespace Microsoft.DotNet.Tools.Test +{ + public class TestRunner : ITestRunner + { + private readonly string _testRunner; + private readonly ICommandFactory _commandFactory; + private readonly ITestRunnerArgumentsBuilder _argumentsBuilder; + + public TestRunner( + string testRunner, + ICommandFactory commandFactory, + ITestRunnerArgumentsBuilder argumentsBuilder) + { + _testRunner = testRunner; + _commandFactory = commandFactory; + _argumentsBuilder = argumentsBuilder; + } + + public void RunTestCommand() + { + ExecuteRunnerCommand(); + } + + public ProcessStartInfo GetProcessStartInfo() + { + var command = CreateTestRunnerCommand(); + + return command.ToProcessStartInfo(); + } + + private void ExecuteRunnerCommand() + { + var result = CreateTestRunnerCommand().Execute(); + + if (result.ExitCode != 0) + { + throw new TestRunnerOperationFailedException(_testRunner, result.ExitCode); + } + } + + private ICommand CreateTestRunnerCommand() + { + var commandArgs = _argumentsBuilder.BuildArguments(); + + return _commandFactory.Create( + _testRunner, + commandArgs, + new NuGetFramework("DNXCore", Version.Parse("5.0"))); + } + } +} diff --git a/src/dotnet/commands/dotnet-test/TestRunners/TestRunnerFactory.cs b/src/dotnet/commands/dotnet-test/TestRunners/TestRunnerFactory.cs new file mode 100644 index 000000000..7c28b910d --- /dev/null +++ b/src/dotnet/commands/dotnet-test/TestRunners/TestRunnerFactory.cs @@ -0,0 +1,24 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Microsoft.DotNet.Cli.Utils; + +namespace Microsoft.DotNet.Tools.Test +{ + public class TestRunnerFactory : ITestRunnerFactory + { + private readonly string _testRunner; + private readonly ICommandFactory _commandFactory; + + public TestRunnerFactory(string testRunner, ICommandFactory commandFactory) + { + _testRunner = testRunner; + _commandFactory = commandFactory; + } + + public ITestRunner CreateTestRunner(ITestRunnerArgumentsBuilder argumentsBuilder) + { + return new TestRunner(_testRunner, _commandFactory, argumentsBuilder); + } + } +} diff --git a/src/dotnet/commands/dotnet-test/TestRunners/TestRunnerOperationFailedException.cs b/src/dotnet/commands/dotnet-test/TestRunners/TestRunnerOperationFailedException.cs new file mode 100644 index 000000000..4a718f328 --- /dev/null +++ b/src/dotnet/commands/dotnet-test/TestRunners/TestRunnerOperationFailedException.cs @@ -0,0 +1,20 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; + +namespace Microsoft.DotNet.Tools.Test +{ + public class TestRunnerOperationFailedException : Exception + { + public string TestRunner { get; set; } + public int ExitCode { get; set; } + public override string Message => $"'{TestRunner}' returned '{ExitCode}'."; + + public TestRunnerOperationFailedException(string testRunner, int exitCode) + { + TestRunner = testRunner; + ExitCode = exitCode; + } + } +} diff --git a/test/dotnet-test.UnitTests/DotnetTestMessageScenario.cs b/test/dotnet-test.UnitTests/DotnetTestMessageScenario.cs new file mode 100644 index 000000000..975ccdf38 --- /dev/null +++ b/test/dotnet-test.UnitTests/DotnetTestMessageScenario.cs @@ -0,0 +1,76 @@ +using Microsoft.DotNet.Cli.Utils; +using Microsoft.DotNet.Tools.Test; +using Microsoft.Extensions.Testing.Abstractions; +using Moq; +using Newtonsoft.Json.Linq; + +namespace Microsoft.Dotnet.Tools.Test.Tests +{ + public class DotnetTestMessageScenario + { + private TestMessagesCollection _messages; + private const string AssemblyUnderTest = "assembly.dll"; + private const string TestRunner = "testRunner"; + private const int Port = 1; + + public DotnetTest DotnetTestUnderTest { get; private set; } + public Mock TestRunnerMock { get; private set; } + public Mock AdapterChannelMock { get; private set; } + public Mock TestRunnerChannelMock { get; private set; } + + public DotnetTestMessageScenario() + { + _messages = new TestMessagesCollection(); + DotnetTestUnderTest = new DotnetTest(_messages, AssemblyUnderTest); + TestRunnerChannelMock = new Mock(); + TestRunnerMock = new Mock(); + AdapterChannelMock = new Mock(); + } + + public void Run() + { + var reportingChannelFactoryMock = new Mock(); + reportingChannelFactoryMock + .Setup(r => r.CreateChannelWithAnyAvailablePort()) + .Returns(TestRunnerChannelMock.Object); + + var commandFactoryMock = new Mock(); + + var testRunnerFactoryMock = new Mock(); + testRunnerFactoryMock + .Setup(t => t.CreateTestRunner(It.IsAny())) + .Returns(TestRunnerMock.Object); + + testRunnerFactoryMock + .Setup(t => t.CreateTestRunner(It.IsAny())) + .Returns(TestRunnerMock.Object); + + var reportingChannelFactory = reportingChannelFactoryMock.Object; + var adapterChannel = AdapterChannelMock.Object; + var commandFactory = commandFactoryMock.Object; + var testRunnerFactory = testRunnerFactoryMock.Object; + + using (DotnetTestUnderTest) + { + DotnetTestUnderTest + .AddNonSpecificMessageHandlers(_messages, adapterChannel) + .AddTestDiscoveryMessageHandlers(adapterChannel, reportingChannelFactory, testRunnerFactory) + .AddTestRunMessageHandlers(adapterChannel, reportingChannelFactory, testRunnerFactory) + .AddTestRunnnersMessageHandlers(adapterChannel); + + DotnetTestUnderTest.StartListeningTo(adapterChannel); + + AdapterChannelMock.Raise(r => r.MessageReceived += null, DotnetTestUnderTest, new Message + { + MessageType = TestMessageTypes.VersionCheck, + Payload = JToken.FromObject(new ProtocolVersionMessage { Version = 1 }) + }); + + DotnetTestUnderTest.StartHandlingMessages(); + } + + AdapterChannelMock.Verify(); + TestRunnerMock.Verify(); + } + } +} \ No newline at end of file diff --git a/test/dotnet-test.UnitTests/GivenADiscoverTestsArgumentsBuilder.cs b/test/dotnet-test.UnitTests/GivenADiscoverTestsArgumentsBuilder.cs new file mode 100644 index 000000000..7ba88c06e --- /dev/null +++ b/test/dotnet-test.UnitTests/GivenADiscoverTestsArgumentsBuilder.cs @@ -0,0 +1,25 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using FluentAssertions; +using Microsoft.DotNet.Tools.Test; +using Xunit; + +namespace Microsoft.Dotnet.Tools.Test.Tests +{ + public class GivenADiscoverTestsArgumentsBuilder + { + [Fact] + public void It_generates_the_right_arguments_for_DiscoverTests() + { + const int port = 1; + const string assembly = "assembly.dll"; + + var discoverTestsArgumentsBuilder = new DiscoverTestsArgumentsBuilder(assembly, port); + + var arguments = discoverTestsArgumentsBuilder.BuildArguments(); + + arguments.Should().BeEquivalentTo(assembly, "--list", "--designtime", "--port", $"{port}"); + } + } +} diff --git a/test/dotnet-test.UnitTests/GivenADotnetTestApp.cs b/test/dotnet-test.UnitTests/GivenADotnetTestApp.cs new file mode 100644 index 000000000..69c501715 --- /dev/null +++ b/test/dotnet-test.UnitTests/GivenADotnetTestApp.cs @@ -0,0 +1,157 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using FluentAssertions; +using Microsoft.DotNet.Tools.Test; +using Microsoft.Extensions.Testing.Abstractions; +using Moq; +using Xunit; + +namespace Microsoft.Dotnet.Tools.Test.Tests +{ + public class GivenADotnetTestApp + { + private const string AssemblyUnderTest = "assembly.dll"; + + private Mock _reportingChannelMock; + private Mock _noOpMessageHandlerMock; + private Mock _realMessageHandlerMock; + private Mock _unknownMessageHandlerMock; + private DotnetTest _dotnetTest; + + public GivenADotnetTestApp() + { + _noOpMessageHandlerMock = new Mock(); + _noOpMessageHandlerMock + .Setup(mh => mh.HandleMessage(It.IsAny(), It.IsAny())) + .Returns(DotnetTestState.NoOp) + .Verifiable(); + + _realMessageHandlerMock = new Mock(); + _realMessageHandlerMock + .Setup(mh => mh.HandleMessage(It.IsAny(), It.Is(m => m.MessageType == "Test message"))) + .Returns(DotnetTestState.VersionCheckCompleted).Callback(() => + _reportingChannelMock.Raise(r => r.MessageReceived += null, _dotnetTest, new Message + { + MessageType = TestMessageTypes.TestSessionTerminate + })); + + _reportingChannelMock = new Mock(); + _unknownMessageHandlerMock = new Mock(); + _unknownMessageHandlerMock + .Setup(mh => mh.HandleMessage(It.IsAny(), It.IsAny())) + .Throws(); + + var testMessagesCollection = new TestMessagesCollection(); + _dotnetTest = new DotnetTest(testMessagesCollection, AssemblyUnderTest) + { + TestSessionTerminateMessageHandler = new TestSessionTerminateMessageHandler(testMessagesCollection), + UnknownMessageHandler = _unknownMessageHandlerMock.Object + }; + + _dotnetTest.StartListeningTo(_reportingChannelMock.Object); + + _reportingChannelMock.Raise(r => r.MessageReceived += null, _dotnetTest, new Message + { + MessageType = "Test message" + }); + } + + [Fact] + public void DotnetTest_handles_TestSession_Terminate_messages_implicitly() + { + _reportingChannelMock.Raise(r => r.MessageReceived += null, _dotnetTest, new Message + { + MessageType = TestMessageTypes.TestSessionTerminate + }); + + _dotnetTest.StartHandlingMessages(); + + //just the fact that we are not hanging means we stopped waiting for messages + } + + [Fact] + public void DotnetTest_calls_each_MessageHandler_until_one_returns_a_state_different_from_NoOp() + { + var secondNoOpMessageHandler = new Mock(); + + _dotnetTest + .AddMessageHandler(_noOpMessageHandlerMock.Object) + .AddMessageHandler(_realMessageHandlerMock.Object) + .AddMessageHandler(secondNoOpMessageHandler.Object); + + _dotnetTest.StartHandlingMessages(); + + _noOpMessageHandlerMock.Verify(); + _realMessageHandlerMock.Verify(); + secondNoOpMessageHandler.Verify( + mh => mh.HandleMessage(It.IsAny(), It.IsAny()), + Times.Never); + } + + [Fact] + public void DotnetTest_does_not_send_an_error_when_the_message_gets_handled() + { + _dotnetTest.AddMessageHandler(_realMessageHandlerMock.Object); + + _dotnetTest.StartHandlingMessages(); + + _reportingChannelMock.Verify(r => r.SendError(It.IsAny()), Times.Never); + } + + [Fact] + public void DotnetTest_calls_the_unknown_message_handler_when_the_message_is_not_handled() + { + _dotnetTest.AddMessageHandler(_noOpMessageHandlerMock.Object); + + Action action = () => _dotnetTest.StartHandlingMessages(); + + action.ShouldThrow(); + } + + [Fact] + public void It_throws_an_InvalidOperationException_if_StartListening_is_called_without_setting_a_TestSessionTerminateMessageHandler() + { + var dotnetTest = new DotnetTest(new TestMessagesCollection(), AssemblyUnderTest) + { + UnknownMessageHandler = new Mock().Object + }; + + Action action = () => dotnetTest.StartListeningTo(new Mock().Object); + + action.ShouldThrow(); + } + + [Fact] + public void It_throws_an_InvalidOperationException_if_StartListeningTo_is_called_without_setting_a_UnknownMessageHandler() + { + var dotnetTest = new DotnetTest(new TestMessagesCollection(), AssemblyUnderTest) + { + TestSessionTerminateMessageHandler = new Mock().Object + }; + + Action action = () => dotnetTest.StartListeningTo(new Mock().Object); + + action.ShouldThrow(); + } + + [Fact] + public void It_disposes_all_reporting_channels_that_it_was_listening_to_when_it_gets_disposed() + { + var firstReportingChannelMock = new Mock(); + var secondReportingChannelMock = new Mock(); + using (var dotnetTest = new DotnetTest(new TestMessagesCollection(), AssemblyUnderTest)) + { + dotnetTest.TestSessionTerminateMessageHandler = new Mock().Object; + dotnetTest.UnknownMessageHandler = new Mock().Object; + + dotnetTest.StartListeningTo(firstReportingChannelMock.Object); + dotnetTest.StartListeningTo(secondReportingChannelMock.Object); + } + + firstReportingChannelMock.Verify(r => r.Dispose(), Times.Once); + secondReportingChannelMock.Verify(r => r.Dispose(), Times.Once); + } + } +} diff --git a/test/dotnet-test.UnitTests/GivenARunTestsArgumentsBuilder.cs b/test/dotnet-test.UnitTests/GivenARunTestsArgumentsBuilder.cs new file mode 100644 index 000000000..31d2f8de8 --- /dev/null +++ b/test/dotnet-test.UnitTests/GivenARunTestsArgumentsBuilder.cs @@ -0,0 +1,41 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Generic; +using FluentAssertions; +using Microsoft.DotNet.Tools.Test; +using Microsoft.Extensions.Testing.Abstractions; +using Newtonsoft.Json.Linq; +using Xunit; + +namespace Microsoft.Dotnet.Tools.Test.Tests +{ + public class GivenARunTestsArgumentsBuilder + { + [Fact] + public void It_generates_the_right_arguments_for_RunTests() + { + const int port = 1; + const string assembly = "assembly.dll"; + + var message = new Message + { + Payload = JToken.FromObject(new RunTestsMessage { Tests = new List { "test1", "test2" } }) + }; + + var runTestsArgumentsBuilder = new RunTestsArgumentsBuilder(assembly, port, message); + + var arguments = runTestsArgumentsBuilder.BuildArguments(); + + arguments.Should().BeEquivalentTo( + assembly, + "--designtime", + "--port", + $"{port}", + "--test", + "test1", + "--test", + "test2"); + } + } +} diff --git a/test/dotnet-test.UnitTests/GivenATestDiscoveryStartMessageHandler.cs b/test/dotnet-test.UnitTests/GivenATestDiscoveryStartMessageHandler.cs new file mode 100644 index 000000000..36cedfe36 --- /dev/null +++ b/test/dotnet-test.UnitTests/GivenATestDiscoveryStartMessageHandler.cs @@ -0,0 +1,152 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using FluentAssertions; +using Microsoft.DotNet.Cli.Tools.Test; +using Microsoft.DotNet.Tools.Test; +using Microsoft.Extensions.Testing.Abstractions; +using Moq; +using Xunit; + +namespace Microsoft.Dotnet.Tools.Test.Tests +{ + public class GivenATestDiscoveryStartMessageHandler + { + private const int TestRunnerPort = 1; + private const string AssemblyUnderTest = "assembly.dll"; + + private TestDiscoveryStartMessageHandler _testDiscoveryStartMessageHandler; + private IDotnetTest _dotnetTestAtVersionCheckCompletedState; + private Message _validMessage; + private Mock _testRunnerFactoryMock; + private Mock _testRunnerMock; + private Mock _adapterChannelMock; + private Mock _testRunnerChannelMock; + private Mock _reportingChannelFactoryMock; + private DiscoverTestsArgumentsBuilder _argumentsBuilder; + private Mock _dotnetTestMock; + + public GivenATestDiscoveryStartMessageHandler() + { + _dotnetTestMock = new Mock(); + _dotnetTestMock.Setup(d => d.State).Returns(DotnetTestState.VersionCheckCompleted); + _dotnetTestMock.Setup(d => d.PathToAssemblyUnderTest).Returns(AssemblyUnderTest); + _dotnetTestAtVersionCheckCompletedState = _dotnetTestMock.Object; + + _testRunnerMock = new Mock(); + _testRunnerFactoryMock = new Mock(); + _testRunnerFactoryMock + .Setup(c => c.CreateTestRunner(It.IsAny())) + .Callback(r => _argumentsBuilder = r as DiscoverTestsArgumentsBuilder) + .Returns(_testRunnerMock.Object); + + _adapterChannelMock = new Mock(); + + _testRunnerChannelMock = new Mock(); + _testRunnerChannelMock.Setup(t => t.Port).Returns(TestRunnerPort); + + _reportingChannelFactoryMock = new Mock(); + _reportingChannelFactoryMock.Setup(r => + r.CreateChannelWithAnyAvailablePort()).Returns(_testRunnerChannelMock.Object); + + _testDiscoveryStartMessageHandler = new TestDiscoveryStartMessageHandler( + _testRunnerFactoryMock.Object, + _adapterChannelMock.Object, + _reportingChannelFactoryMock.Object); + + _validMessage = new Message + { + MessageType = TestMessageTypes.TestDiscoveryStart + }; + } + + [Fact] + public void It_returns_NoOp_if_the_dotnet_test_state_is_not_VersionCheckCompleted() + { + var dotnetTestMock = new Mock(); + dotnetTestMock.Setup(d => d.State).Returns(DotnetTestState.Terminated); + + var nextState = _testDiscoveryStartMessageHandler.HandleMessage( + dotnetTestMock.Object, + new Message { MessageType = TestMessageTypes.TestDiscoveryStart }); + + nextState.Should().Be(DotnetTestState.NoOp); + } + + [Fact] + public void It_returns_NoOp_if_the_message_is_not_TestDiscoveryStart() + { + var nextState = _testDiscoveryStartMessageHandler.HandleMessage( + _dotnetTestAtVersionCheckCompletedState, + new Message { MessageType = "Something different from TestDiscovery.Start" }); + + nextState.Should().Be(DotnetTestState.NoOp); + } + + [Fact] + public void It_returns_TestDiscoveryCompleted_when_it_handles_the_message() + { + var nextState = + _testDiscoveryStartMessageHandler.HandleMessage(_dotnetTestAtVersionCheckCompletedState, _validMessage); + + nextState.Should().Be(DotnetTestState.TestDiscoveryStarted); + } + + [Fact] + public void It_uses_the_test_runner_to_discover_tests_when_it_handles_the_message() + { + _testDiscoveryStartMessageHandler.HandleMessage(_dotnetTestAtVersionCheckCompletedState, _validMessage); + + _testRunnerMock.Verify(t => t.RunTestCommand(), Times.Once); + } + + [Fact] + public void It_sends_an_error_when_the_test_runner_fails() + { + const string testRunner = "SomeTestRunner"; + + _testRunnerMock.Setup(t => t.RunTestCommand()).Throws(new TestRunnerOperationFailedException(testRunner, 1)); + + _testDiscoveryStartMessageHandler.HandleMessage(_dotnetTestAtVersionCheckCompletedState, _validMessage); + + _adapterChannelMock.Verify(r => r.SendError($"'{testRunner}' returned '1'."), Times.Once); + } + + [Fact] + public void It_creates_a_new_reporting_channel() + { + _testDiscoveryStartMessageHandler.HandleMessage( + _dotnetTestMock.Object, + _validMessage); + + _reportingChannelFactoryMock.Verify(r => r.CreateChannelWithAnyAvailablePort(), Times.Once); + } + + [Fact] + public void It_makes_dotnet_test_listen_on_the_test_runner_port_for_messages_when_it_handles_the_message() + { + _testDiscoveryStartMessageHandler.HandleMessage( + _dotnetTestMock.Object, + _validMessage); + + _dotnetTestMock.Verify(d => d.StartListeningTo(_testRunnerChannelMock.Object), Times.Once); + } + + [Fact] + public void It_passes_the_right_arguments_to_the_run_tests_arguments_builder() + { + _testDiscoveryStartMessageHandler.HandleMessage( + _dotnetTestMock.Object, + _validMessage); + + _argumentsBuilder.Should().NotBeNull(); + + var arguments = _argumentsBuilder.BuildArguments(); + + arguments.Should().Contain("--port", $"{TestRunnerPort}"); + arguments.Should().Contain($"{AssemblyUnderTest}"); + arguments.Should().Contain("--list"); + arguments.Should().Contain("--designtime"); + } + } +} diff --git a/test/dotnet-test.UnitTests/GivenATestExecutionGetTestRunnerProcessStartInfoMessageHandler.cs b/test/dotnet-test.UnitTests/GivenATestExecutionGetTestRunnerProcessStartInfoMessageHandler.cs new file mode 100644 index 000000000..be0f7555b --- /dev/null +++ b/test/dotnet-test.UnitTests/GivenATestExecutionGetTestRunnerProcessStartInfoMessageHandler.cs @@ -0,0 +1,165 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Generic; +using System.Diagnostics; +using FluentAssertions; +using Microsoft.DotNet.Tools.Test; +using Microsoft.Extensions.Testing.Abstractions; +using Moq; +using Newtonsoft.Json.Linq; +using Xunit; + +namespace Microsoft.Dotnet.Tools.Test.Tests +{ + public class GivenATestExecutionGetTestRunnerProcessStartInfoMessageHandler + { + private const int TestRunnerPort = 1; + private const string AssemblyUnderTest = "assembly.dll"; + + private GetTestRunnerProcessStartInfoMessageHandler _testGetTestRunnerProcessStartInfoMessageHandler; + private Message _validMessage; + private ProcessStartInfo _processStartInfo; + + private Mock _testRunnerMock; + private Mock _testRunnerFactoryMock; + private Mock _adapterChannelMock; + private Mock _testRunnerChannelMock; + private Mock _reportingChannelFactoryMock; + private Mock _dotnetTestMock; + + private RunTestsArgumentsBuilder _argumentsBuilder; + + public GivenATestExecutionGetTestRunnerProcessStartInfoMessageHandler() + { + _validMessage = new Message + { + MessageType = TestMessageTypes.TestExecutionGetTestRunnerProcessStartInfo, + Payload = JToken.FromObject(new RunTestsMessage { Tests = new List { "test1", "test2" } }) + }; + + _dotnetTestMock = new Mock(); + _dotnetTestMock.Setup(d => d.State).Returns(DotnetTestState.VersionCheckCompleted); + _dotnetTestMock.Setup(d => d.PathToAssemblyUnderTest).Returns(AssemblyUnderTest); + + _processStartInfo = new ProcessStartInfo("runner", "arguments"); + + _testRunnerMock = new Mock(); + _testRunnerMock.Setup(t => t.GetProcessStartInfo()).Returns(_processStartInfo); + + _testRunnerFactoryMock = new Mock(); + _testRunnerFactoryMock + .Setup(c => c.CreateTestRunner(It.IsAny())) + .Callback(r => _argumentsBuilder = r as RunTestsArgumentsBuilder) + .Returns(_testRunnerMock.Object); + + _adapterChannelMock = new Mock(); + _testRunnerChannelMock = new Mock(); + _testRunnerChannelMock.Setup(t => t.Port).Returns(TestRunnerPort); + + _reportingChannelFactoryMock = new Mock(); + _reportingChannelFactoryMock.Setup(r => + r.CreateChannelWithAnyAvailablePort()).Returns(_testRunnerChannelMock.Object); + + _testGetTestRunnerProcessStartInfoMessageHandler = new GetTestRunnerProcessStartInfoMessageHandler( + _testRunnerFactoryMock.Object, + _adapterChannelMock.Object, + _reportingChannelFactoryMock.Object); + } + + [Fact] + public void It_returns_NoOp_if_the_dotnet_test_state_is_not_VersionCheckCompleted() + { + var dotnetTestMock = new Mock(); + dotnetTestMock.Setup(d => d.State).Returns(DotnetTestState.Terminated); + + var nextState = _testGetTestRunnerProcessStartInfoMessageHandler.HandleMessage( + dotnetTestMock.Object, + _validMessage); + + nextState.Should().Be(DotnetTestState.NoOp); + } + + [Fact] + public void It_returns_NoOp_if_the_message_is_not_TestDiscoveryStart() + { + var nextState = _testGetTestRunnerProcessStartInfoMessageHandler.HandleMessage( + _dotnetTestMock.Object, + new Message { MessageType = "Something different from TestDiscovery.Start" }); + + nextState.Should().Be(DotnetTestState.NoOp); + } + + [Fact] + public void It_returns_TestExecutionSentTestRunnerProcessStartInfo_when_it_handles_the_message() + { + var nextState = _testGetTestRunnerProcessStartInfoMessageHandler.HandleMessage( + _dotnetTestMock.Object, + _validMessage); + + nextState.Should().Be(DotnetTestState.TestExecutionSentTestRunnerProcessStartInfo); + } + + [Fact] + public void It_gets_the_process_start_info_from_the_test_runner_when_it_handles_the_message() + { + _testGetTestRunnerProcessStartInfoMessageHandler.HandleMessage( + _dotnetTestMock.Object, + _validMessage); + + _testRunnerMock.Verify(t => t.GetProcessStartInfo(), Times.Once); + } + + [Fact] + public void It_sends_the_process_start_info_when_it_handles_the_message() + { + _adapterChannelMock.Setup(r => r.Send(It.Is(m => + m.MessageType == TestMessageTypes.TestExecutionTestRunnerProcessStartInfo && + m.Payload.ToObject().FileName == _processStartInfo.FileName && + m.Payload.ToObject().Arguments == _processStartInfo.Arguments))).Verifiable(); + + _testGetTestRunnerProcessStartInfoMessageHandler.HandleMessage( + _dotnetTestMock.Object, + _validMessage); + + _adapterChannelMock.Verify(); + } + + [Fact] + public void It_creates_a_new_reporting_channel() + { + _testGetTestRunnerProcessStartInfoMessageHandler.HandleMessage( + _dotnetTestMock.Object, + _validMessage); + + _reportingChannelFactoryMock.Verify(r => r.CreateChannelWithAnyAvailablePort(), Times.Once); + } + + [Fact] + public void It_makes_dotnet_test_listen_on_the_test_runner_port_for_messages_when_it_handles_the_message() + { + _testGetTestRunnerProcessStartInfoMessageHandler.HandleMessage( + _dotnetTestMock.Object, + _validMessage); + + _dotnetTestMock.Verify(d => d.StartListeningTo(_testRunnerChannelMock.Object), Times.Once); + } + + [Fact] + public void It_passes_the_right_arguments_to_the_run_tests_arguments_builder() + { + _testGetTestRunnerProcessStartInfoMessageHandler.HandleMessage( + _dotnetTestMock.Object, + _validMessage); + + _argumentsBuilder.Should().NotBeNull(); + + var arguments = _argumentsBuilder.BuildArguments(); + + arguments.Should().Contain("--port", $"{TestRunnerPort}"); + arguments.Should().Contain($"{AssemblyUnderTest}"); + arguments.Should().Contain("--test", "test1"); + arguments.Should().Contain("--test", "test2"); + } + } +} diff --git a/test/dotnet-test.UnitTests/GivenATestRunner.cs b/test/dotnet-test.UnitTests/GivenATestRunner.cs new file mode 100644 index 000000000..d7a095c8d --- /dev/null +++ b/test/dotnet-test.UnitTests/GivenATestRunner.cs @@ -0,0 +1,106 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using FluentAssertions; +using Microsoft.DotNet.Cli.Utils; +using Microsoft.DotNet.Tools.Test; +using Moq; +using NuGet.Frameworks; +using Xunit; +using Newtonsoft.Json; +using Microsoft.Extensions.Testing.Abstractions; +using System.Linq; +using Newtonsoft.Json.Linq; + +namespace Microsoft.Dotnet.Tools.Test.Tests +{ + public class GivenATestRunner + { + private Mock _commandMock; + private Mock _commandFactoryMock; + private Mock _argumentsBuilderMock; + private string _runner = "runner"; + private string[] _testRunnerArguments; + + public GivenATestRunner() + { + _testRunnerArguments = new[] {"assembly.dll", "--list", "--designtime"}; + + _commandMock = new Mock(); + _commandMock.Setup(c => c.CommandName).Returns(_runner); + _commandMock.Setup(c => c.CommandArgs).Returns(string.Join(" ", _testRunnerArguments)); + _commandMock.Setup(c => c.OnOutputLine(It.IsAny>())).Returns(_commandMock.Object); + + _argumentsBuilderMock = new Mock(); + _argumentsBuilderMock.Setup(a => a.BuildArguments()) + .Returns(_testRunnerArguments); + + _commandFactoryMock = new Mock(); + _commandFactoryMock.Setup(c => c.Create( + _runner, + _testRunnerArguments, + new NuGetFramework("DNXCore", Version.Parse("5.0")), + null)).Returns(_commandMock.Object).Verifiable(); + } + + [Fact] + public void It_creates_a_command_using_the_right_parameters() + { + var testRunner = new TestRunner(_runner, _commandFactoryMock.Object, _argumentsBuilderMock.Object); + + testRunner.RunTestCommand(); + + _commandFactoryMock.Verify(); + } + + [Fact] + public void It_executes_the_command() + { + var testRunner = new TestRunner(_runner, _commandFactoryMock.Object, _argumentsBuilderMock.Object); + + testRunner.RunTestCommand(); + + _commandMock.Verify(c => c.Execute(), Times.Once); + } + + [Fact] + public void It_throws_TestRunnerOperationFailedException_when_the_returns_return_an_error_code() + { + _commandMock.Setup(c => c.Execute()).Returns(new CommandResult(null, 1, null, null)); + + var testRunner = new TestRunner(_runner, _commandFactoryMock.Object, _argumentsBuilderMock.Object); + + Action action = () => testRunner.RunTestCommand(); + + action.ShouldThrow(); + } + + [Fact] + public void It_executes_the_command_when_RunTestCommand_is_called() + { + var testResult = new Message + { + MessageType = "Irrelevant", + Payload = JToken.FromObject("Irrelevant") + }; + + var testRunner = new TestRunner(_runner, _commandFactoryMock.Object, _argumentsBuilderMock.Object); + + testRunner.RunTestCommand(); + + _commandMock.Verify(c => c.Execute(), Times.Once); + } + + [Fact] + public void It_returns_a_ProcessStartInfo_object_with_the_right_parameters_to_execute_the_test_command() + { + var testRunner = new TestRunner(_runner, _commandFactoryMock.Object, _argumentsBuilderMock.Object); + + var testCommandProcessStartInfo = testRunner.GetProcessStartInfo(); + + testCommandProcessStartInfo.FileName.Should().Be(_runner); + testCommandProcessStartInfo.Arguments.Should().Be(string.Join(" ", _testRunnerArguments)); + } + } +} diff --git a/test/dotnet-test.UnitTests/GivenATestRunnerTestCompletedMessageHandler.cs b/test/dotnet-test.UnitTests/GivenATestRunnerTestCompletedMessageHandler.cs new file mode 100644 index 000000000..9c855c204 --- /dev/null +++ b/test/dotnet-test.UnitTests/GivenATestRunnerTestCompletedMessageHandler.cs @@ -0,0 +1,122 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using FluentAssertions; +using Microsoft.DotNet.Tools.Test; +using Microsoft.Extensions.Testing.Abstractions; +using Moq; +using Newtonsoft.Json.Linq; +using Xunit; + +namespace Microsoft.Dotnet.Tools.Test.Tests +{ + public class GivenATestRunnerTestCompletedMessageHandler + { + private Mock _dotnetTestAtTestDiscoveryStartedMock; + private Mock _dotnetTestAtTestExecutionStartedMock; + private Mock _adapterChannelMock; + + private Message _validMessage; + private TestRunnerTestCompletedMessageHandler _testRunnerTestCompletedMessageHandler; + + public GivenATestRunnerTestCompletedMessageHandler() + { + _dotnetTestAtTestDiscoveryStartedMock = new Mock(); + _dotnetTestAtTestDiscoveryStartedMock.Setup(d => d.State).Returns(DotnetTestState.TestDiscoveryStarted); + + _dotnetTestAtTestExecutionStartedMock = new Mock(); + _dotnetTestAtTestExecutionStartedMock.Setup(d => d.State).Returns(DotnetTestState.TestExecutionStarted); + + _adapterChannelMock = new Mock(); + + _validMessage = new Message + { + MessageType = TestMessageTypes.TestRunnerTestCompleted + }; + + _testRunnerTestCompletedMessageHandler = + new TestRunnerTestCompletedMessageHandler(_adapterChannelMock.Object); + } + + [Fact] + public void It_returns_NoOp_if_the_dotnet_test_state_is_not_TestDiscoveryStarted_or_TestExecutionStarted() + { + var dotnetTestMock = new Mock(); + dotnetTestMock.Setup(d => d.State).Returns(DotnetTestState.Terminated); + + var nextState = _testRunnerTestCompletedMessageHandler.HandleMessage( + dotnetTestMock.Object, + _validMessage); + + nextState.Should().Be(DotnetTestState.NoOp); + } + + [Fact] + public void It_returns_NoOp_if_the_message_is_not_TestRunnerTestCompleted_when_state_is_TestDiscoveryStarted() + { + var nextState = _testRunnerTestCompletedMessageHandler.HandleMessage( + _dotnetTestAtTestDiscoveryStartedMock.Object, + new Message { MessageType = "Something different from TestDiscovery.Start" }); + + nextState.Should().Be(DotnetTestState.NoOp); + } + + [Fact] + public void It_returns_NoOp_if_the_message_is_not_TestRunnerTestCompleted_when_state_is_TestExecutionStarted() + { + var nextState = _testRunnerTestCompletedMessageHandler.HandleMessage( + _dotnetTestAtTestExecutionStartedMock.Object, + new Message { MessageType = "Something different from TestDiscovery.Start" }); + + nextState.Should().Be(DotnetTestState.NoOp); + } + + [Fact] + public void It_returns_TestDiscoveryCompleted_when_it_handles_the_message_and_current_state_is_TestDiscoveryStarted() + { + var nextState = _testRunnerTestCompletedMessageHandler.HandleMessage( + _dotnetTestAtTestDiscoveryStartedMock.Object, + _validMessage); + + nextState.Should().Be(DotnetTestState.TestDiscoveryCompleted); + } + + [Fact] + public void It_sends_a_TestDiscoveryCompleted_when_it_handles_the_message_and_current_state_is_TestDiscoveryStarted() + { + _adapterChannelMock + .Setup(a => a.Send(It.Is(m => m.MessageType == TestMessageTypes.TestDiscoveryCompleted))) + .Verifiable(); + + _testRunnerTestCompletedMessageHandler.HandleMessage( + _dotnetTestAtTestDiscoveryStartedMock.Object, + _validMessage); + + _adapterChannelMock.Verify(); + } + + [Fact] + public void It_returns_TestExecutionCompleted_when_it_handles_the_message_and_current_state_is_TestExecutionStarted() + { + var nextState = _testRunnerTestCompletedMessageHandler.HandleMessage( + _dotnetTestAtTestExecutionStartedMock.Object, + _validMessage); + + nextState.Should().Be(DotnetTestState.TestExecutionCompleted); + } + + [Fact] + public void It_sends_a_TestExecutionCompleted_when_it_handles_the_message_and_current_state_is_TestExecutionStarted() + { + _adapterChannelMock + .Setup(a => a.Send(It.Is(m => m.MessageType == TestMessageTypes.TestExecutionCompleted))) + .Verifiable(); + + _testRunnerTestCompletedMessageHandler.HandleMessage( + _dotnetTestAtTestExecutionStartedMock.Object, + _validMessage); + + _adapterChannelMock.Verify(); + } + } +} diff --git a/test/dotnet-test.UnitTests/GivenATestRunnerTestFoundMessageHandler.cs b/test/dotnet-test.UnitTests/GivenATestRunnerTestFoundMessageHandler.cs new file mode 100644 index 000000000..8a9c98c67 --- /dev/null +++ b/test/dotnet-test.UnitTests/GivenATestRunnerTestFoundMessageHandler.cs @@ -0,0 +1,84 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using FluentAssertions; +using Microsoft.DotNet.Tools.Test; +using Microsoft.Extensions.Testing.Abstractions; +using Moq; +using Newtonsoft.Json.Linq; +using Xunit; + +namespace Microsoft.Dotnet.Tools.Test.Tests +{ + public class GivenATestRunnerTestFoundMessageHandler + { + private Mock _dotnetTestMock; + private Mock _adapterChannelMock; + + private Message _validMessage; + private TestRunnerTestFoundMessageHandler _testRunnerTestFoundMessageHandler; + + public GivenATestRunnerTestFoundMessageHandler() + { + _dotnetTestMock = new Mock(); + _dotnetTestMock.Setup(d => d.State).Returns(DotnetTestState.TestDiscoveryStarted); + + _adapterChannelMock = new Mock(); + + _validMessage = new Message + { + MessageType = TestMessageTypes.TestRunnerTestFound, + Payload = JToken.FromObject("testFound") + }; + + _testRunnerTestFoundMessageHandler = new TestRunnerTestFoundMessageHandler(_adapterChannelMock.Object); + } + + [Fact] + public void It_returns_NoOp_if_the_dotnet_test_state_is_not_TestDiscoveryStarted() + { + var dotnetTestMock = new Mock(); + dotnetTestMock.Setup(d => d.State).Returns(DotnetTestState.Terminated); + + var nextState = _testRunnerTestFoundMessageHandler.HandleMessage( + dotnetTestMock.Object, + _validMessage); + + nextState.Should().Be(DotnetTestState.NoOp); + } + + [Fact] + public void It_returns_NoOp_if_the_message_is_not_TestRunnerTestFound() + { + var nextState = _testRunnerTestFoundMessageHandler.HandleMessage( + _dotnetTestMock.Object, + new Message { MessageType = "Something different from TestDiscovery.Start" }); + + nextState.Should().Be(DotnetTestState.NoOp); + } + + [Fact] + public void It_returns_TestDiscoveryStarted_when_it_handles_the_message() + { + var nextState = _testRunnerTestFoundMessageHandler.HandleMessage( + _dotnetTestMock.Object, + _validMessage); + + nextState.Should().Be(DotnetTestState.TestDiscoveryStarted); + } + + [Fact] + public void It_sends_the_payload_of_the_message_when_it_handles_the_message() + { + _adapterChannelMock.Setup(a => a.Send(It.Is(m => + m.MessageType == TestMessageTypes.TestDiscoveryTestFound && + m.Payload.ToObject() == _validMessage.Payload.ToObject()))).Verifiable(); + + _testRunnerTestFoundMessageHandler.HandleMessage( + _dotnetTestMock.Object, + _validMessage); + + _adapterChannelMock.Verify(); + } + } +} diff --git a/test/dotnet-test.UnitTests/GivenATestRunnerTestResultMessageHandler.cs b/test/dotnet-test.UnitTests/GivenATestRunnerTestResultMessageHandler.cs new file mode 100644 index 000000000..11700dac9 --- /dev/null +++ b/test/dotnet-test.UnitTests/GivenATestRunnerTestResultMessageHandler.cs @@ -0,0 +1,84 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using FluentAssertions; +using Microsoft.DotNet.Tools.Test; +using Microsoft.Extensions.Testing.Abstractions; +using Moq; +using Newtonsoft.Json.Linq; +using Xunit; + +namespace Microsoft.Dotnet.Tools.Test.Tests +{ + public class GivenATestRunnerTestResultMessageHandler + { + private Mock _dotnetTestMock; + private Mock _adapterChannelMock; + + private Message _validMessage; + private TestRunnerTestResultMessageHandler _testRunnerTestResultMessageHandler; + + public GivenATestRunnerTestResultMessageHandler() + { + _dotnetTestMock = new Mock(); + _dotnetTestMock.Setup(d => d.State).Returns(DotnetTestState.TestExecutionStarted); + + _adapterChannelMock = new Mock(); + + _validMessage = new Message + { + MessageType = TestMessageTypes.TestRunnerTestResult, + Payload = JToken.FromObject("testFound") + }; + + _testRunnerTestResultMessageHandler = new TestRunnerTestResultMessageHandler(_adapterChannelMock.Object); + } + + [Fact] + public void It_returns_NoOp_if_the_dotnet_test_state_is_not_TestExecutionStarted() + { + var dotnetTestMock = new Mock(); + dotnetTestMock.Setup(d => d.State).Returns(DotnetTestState.Terminated); + + var nextState = _testRunnerTestResultMessageHandler.HandleMessage( + dotnetTestMock.Object, + _validMessage); + + nextState.Should().Be(DotnetTestState.NoOp); + } + + [Fact] + public void It_returns_NoOp_if_the_message_is_not_TestRunnerTestResult() + { + var nextState = _testRunnerTestResultMessageHandler.HandleMessage( + _dotnetTestMock.Object, + new Message { MessageType = "Something different from TestRunner.TestResult" }); + + nextState.Should().Be(DotnetTestState.NoOp); + } + + [Fact] + public void It_returns_TestExecutionStarted_when_it_handles_the_message() + { + var nextState = _testRunnerTestResultMessageHandler.HandleMessage( + _dotnetTestMock.Object, + _validMessage); + + nextState.Should().Be(DotnetTestState.TestExecutionStarted); + } + + [Fact] + public void It_sends_the_payload_of_the_message_when_it_handles_the_message() + { + _adapterChannelMock.Setup(a => a.Send(It.Is(m => + m.MessageType == TestMessageTypes.TestExecutionTestResult && + m.Payload.ToObject() == _validMessage.Payload.ToObject()))).Verifiable(); + + _testRunnerTestResultMessageHandler.HandleMessage( + _dotnetTestMock.Object, + _validMessage); + + _adapterChannelMock.Verify(); + } + } +} diff --git a/test/dotnet-test.UnitTests/GivenATestRunnerTestStartedMessageHandler.cs b/test/dotnet-test.UnitTests/GivenATestRunnerTestStartedMessageHandler.cs new file mode 100644 index 000000000..0d8d2da0f --- /dev/null +++ b/test/dotnet-test.UnitTests/GivenATestRunnerTestStartedMessageHandler.cs @@ -0,0 +1,99 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using FluentAssertions; +using Microsoft.DotNet.Tools.Test; +using Microsoft.Extensions.Testing.Abstractions; +using Moq; +using Newtonsoft.Json.Linq; +using Xunit; + +namespace Microsoft.Dotnet.Tools.Test.Tests +{ + public class GivenATestRunnerTestStartedMessageHandler + { + private Mock _dotnetTestMock; + private Mock _adapterChannelMock; + + private Message _validMessage; + private TestRunnerTestStartedMessageHandler _testRunnerTestStartedMessageHandler; + + public GivenATestRunnerTestStartedMessageHandler() + { + _dotnetTestMock = new Mock(); + _dotnetTestMock.Setup(d => d.State).Returns(DotnetTestState.TestExecutionSentTestRunnerProcessStartInfo); + + _adapterChannelMock = new Mock(); + + _validMessage = new Message + { + MessageType = TestMessageTypes.TestRunnerTestStarted, + Payload = JToken.FromObject("testFound") + }; + + _testRunnerTestStartedMessageHandler = + new TestRunnerTestStartedMessageHandler(_adapterChannelMock.Object); + } + + [Fact] + public void It_returns_NoOp_if_the_dotnet_test_state_is_not_TestExecutionSentTestRunnerProcessStartInfo() + { + var dotnetTestMock = new Mock(); + dotnetTestMock.Setup(d => d.State).Returns(DotnetTestState.Terminated); + + var nextState = _testRunnerTestStartedMessageHandler.HandleMessage( + dotnetTestMock.Object, + _validMessage); + + nextState.Should().Be(DotnetTestState.NoOp); + } + + [Fact] + public void It_returns_NoOp_if_the_message_is_not_TestRunnerTestStarted() + { + var nextState = _testRunnerTestStartedMessageHandler.HandleMessage( + _dotnetTestMock.Object, + new Message { MessageType = "Something different from TestRunner.TestStart" }); + + nextState.Should().Be(DotnetTestState.NoOp); + } + + [Fact] + public void It_returns_TestExecutionStarted_when_it_handles_the_message() + { + var nextState = _testRunnerTestStartedMessageHandler.HandleMessage( + _dotnetTestMock.Object, + _validMessage); + + nextState.Should().Be(DotnetTestState.TestExecutionStarted); + } + + [Fact] + public void It_sends_a_TestExecutionTestStarted_when_it_handles_the_message() + { + _adapterChannelMock + .Setup(a => a.Send(It.Is(m => m.MessageType == TestMessageTypes.TestExecutionStarted))) + .Verifiable(); + + _testRunnerTestStartedMessageHandler.HandleMessage( + _dotnetTestMock.Object, + _validMessage); + + _adapterChannelMock.Verify(); + } + + [Fact] + public void It_sends_the_payload_of_the_message_when_it_handles_the_message() + { + _adapterChannelMock.Setup(a => a.Send(It.Is(m => + m.MessageType == TestMessageTypes.TestExecutionStarted && + m.Payload.ToObject() == _validMessage.Payload.ToObject()))).Verifiable(); + + _testRunnerTestStartedMessageHandler.HandleMessage( + _dotnetTestMock.Object, + _validMessage); + + _adapterChannelMock.Verify(); + } + } +} diff --git a/test/dotnet-test.UnitTests/GivenATestSessionTerminateMessageHandler.cs b/test/dotnet-test.UnitTests/GivenATestSessionTerminateMessageHandler.cs new file mode 100644 index 000000000..febfb3b9e --- /dev/null +++ b/test/dotnet-test.UnitTests/GivenATestSessionTerminateMessageHandler.cs @@ -0,0 +1,42 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using FluentAssertions; +using Microsoft.DotNet.Tools.Test; +using Microsoft.Extensions.Testing.Abstractions; +using Moq; +using Xunit; + +namespace Microsoft.Dotnet.Tools.Test.Tests +{ + public class GivenATestSessionTerminateMessageHandler + { + private DotnetTestState _nextState; + private Mock _testMessagesCollectionMock; + + public GivenATestSessionTerminateMessageHandler() + { + var reportingChannel = new Mock(); + _testMessagesCollectionMock = new Mock(); + var dotnetTestMock = new Mock(); + var messageHandler = new TestSessionTerminateMessageHandler(_testMessagesCollectionMock.Object); + + _nextState = messageHandler.HandleMessage(dotnetTestMock.Object, new Message + { + MessageType = TestMessageTypes.TestSessionTerminate + }); + } + + [Fact] + public void It_always_returns_the_terminated_state_idependent_of_the_state_passed_to_it() + { + _nextState.Should().Be(DotnetTestState.Terminated); + } + + [Fact] + public void It_calls_drain_on_the_test_messages() + { + _testMessagesCollectionMock.Verify(tmc => tmc.Drain(), Times.Once); + } + } +} diff --git a/test/dotnet-test.UnitTests/GivenAUnknownMessageHandler.cs b/test/dotnet-test.UnitTests/GivenAUnknownMessageHandler.cs new file mode 100644 index 000000000..ff9200f37 --- /dev/null +++ b/test/dotnet-test.UnitTests/GivenAUnknownMessageHandler.cs @@ -0,0 +1,37 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using Microsoft.DotNet.Tools.Test; +using Microsoft.Extensions.Testing.Abstractions; +using Moq; +using Xunit; +using FluentAssertions; + +namespace Microsoft.Dotnet.Tools.Test.Tests +{ + public class GivenAUnknownMessageHandler + { + [Fact] + public void It_throws_InvalidOperationException_and_sends_an_error_when_the_message_is_not_handled() + { + const string expectedError = "No handler for message 'Test Message' when at state 'InitialState'"; + + var dotnetTestMock = new Mock(); + dotnetTestMock.Setup(d => d.State).Returns(DotnetTestState.InitialState); + + var reportingChannel = new Mock(); + reportingChannel.Setup(r => r.SendError(expectedError)).Verifiable(); + + var unknownMessageHandler = new UnknownMessageHandler(reportingChannel.Object); + + Action action = () => unknownMessageHandler.HandleMessage( + dotnetTestMock.Object, + new Message { MessageType = "Test Message" }); + + action.ShouldThrow().WithMessage(expectedError); + + reportingChannel.Verify(); + } + } +} diff --git a/test/dotnet-test.UnitTests/GivenAVersionCheckMessageHandler.cs b/test/dotnet-test.UnitTests/GivenAVersionCheckMessageHandler.cs new file mode 100644 index 000000000..19bf77653 --- /dev/null +++ b/test/dotnet-test.UnitTests/GivenAVersionCheckMessageHandler.cs @@ -0,0 +1,83 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using FluentAssertions; +using Microsoft.DotNet.Tools.Test; +using Microsoft.Extensions.Testing.Abstractions; +using Moq; +using Newtonsoft.Json.Linq; +using Xunit; + +namespace Microsoft.Dotnet.Tools.Test.Tests +{ + public class GivenAVersionCheckMessageHandler + { + private Mock _reportingChannelMock; + private VersionCheckMessageHandler _versionCheckMessageHandler; + private Message _validMessage; + private IDotnetTest _dotnetTestAtInitialState; + + public GivenAVersionCheckMessageHandler() + { + _reportingChannelMock = new Mock(); + _versionCheckMessageHandler = new VersionCheckMessageHandler(_reportingChannelMock.Object); + + _validMessage = new Message + { + MessageType = TestMessageTypes.VersionCheck, + Payload = JToken.FromObject(new ProtocolVersionMessage + { + Version = 99 + }) + }; + + var dotnetTestAtInitialStateMock = new Mock(); + dotnetTestAtInitialStateMock.Setup(d => d.State).Returns(DotnetTestState.InitialState); + _dotnetTestAtInitialState = dotnetTestAtInitialStateMock.Object; + } + + [Fact] + public void It_returns_NoOp_if_the_dotnet_test_state_is_not_initial() + { + var dotnetTestMock = new Mock(); + dotnetTestMock.Setup(d => d.State).Returns(DotnetTestState.Terminated); + + var nextState = _versionCheckMessageHandler.HandleMessage( + dotnetTestMock.Object, + new Message {MessageType = TestMessageTypes.VersionCheck}); + + nextState.Should().Be(DotnetTestState.NoOp); + } + + [Fact] + public void It_returns_NoOp_if_the_message_is_not_VersionCheck() + { + var nextState = _versionCheckMessageHandler.HandleMessage( + _dotnetTestAtInitialState, + new Message { MessageType = "Something different from ProtocolVersion" }); + + nextState.Should().Be(DotnetTestState.NoOp); + } + + [Fact] + public void It_returns_VersionCheckCompleted_when_it_handles_the_message() + { + var nextState = _versionCheckMessageHandler.HandleMessage(_dotnetTestAtInitialState, _validMessage); + + nextState.Should().Be(DotnetTestState.VersionCheckCompleted); + } + + [Fact] + public void It_returns_a_ProtocolVersion_with_the_SupportedVersion_when_it_handles_the_message() + { + _reportingChannelMock.Setup(r => + r.Send(It.Is(m => + m.MessageType == TestMessageTypes.VersionCheck && + m.Payload.ToObject().Version == 1))).Verifiable(); + + _versionCheckMessageHandler.HandleMessage(_dotnetTestAtInitialState, _validMessage); + + _reportingChannelMock.Verify(); + } + } +} diff --git a/test/dotnet-test.UnitTests/GivenThatWeWantToDiscoverTests.cs b/test/dotnet-test.UnitTests/GivenThatWeWantToDiscoverTests.cs new file mode 100644 index 000000000..6ffb0b0c8 --- /dev/null +++ b/test/dotnet-test.UnitTests/GivenThatWeWantToDiscoverTests.cs @@ -0,0 +1,67 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Microsoft.DotNet.Tools.Test; +using Microsoft.Extensions.Testing.Abstractions; +using Moq; +using Newtonsoft.Json.Linq; +using Xunit; + +namespace Microsoft.Dotnet.Tools.Test.Tests +{ + public class GivenThatWeWantToDiscoverTests + { + [Fact] + public void Dotnet_test_handles_and_sends_all_the_right_messages() + { + var dotnetTestMessageScenario = new DotnetTestMessageScenario(); + + dotnetTestMessageScenario.TestRunnerMock + .Setup(t => t.RunTestCommand()) + .Callback(() => dotnetTestMessageScenario.TestRunnerChannelMock.Raise( + t => t.MessageReceived += null, + dotnetTestMessageScenario.DotnetTestUnderTest, + new Message + { + MessageType = TestMessageTypes.TestRunnerTestFound, + Payload = JToken.FromObject("testFound") + })) + .Verifiable(); + + dotnetTestMessageScenario.AdapterChannelMock + .Setup(a => a.Send(It.Is(m => m.MessageType == TestMessageTypes.VersionCheck))) + .Callback(() => dotnetTestMessageScenario.AdapterChannelMock.Raise( + r => r.MessageReceived += null, + dotnetTestMessageScenario.DotnetTestUnderTest, + new Message + { + MessageType = TestMessageTypes.TestDiscoveryStart + })) + .Verifiable(); + + dotnetTestMessageScenario.AdapterChannelMock + .Setup(a => a.Send(It.Is(m => m.MessageType == TestMessageTypes.TestDiscoveryTestFound))) + .Callback(() => dotnetTestMessageScenario.TestRunnerChannelMock.Raise( + t => t.MessageReceived += null, + dotnetTestMessageScenario.DotnetTestUnderTest, + new Message + { + MessageType = TestMessageTypes.TestRunnerTestCompleted + })) + .Verifiable(); + + dotnetTestMessageScenario.AdapterChannelMock + .Setup(a => a.Send(It.Is(m => m.MessageType == TestMessageTypes.TestDiscoveryCompleted))) + .Callback(() => dotnetTestMessageScenario.AdapterChannelMock.Raise( + r => r.MessageReceived += null, + dotnetTestMessageScenario.DotnetTestUnderTest, + new Message + { + MessageType = TestMessageTypes.TestSessionTerminate + })) + .Verifiable(); + + dotnetTestMessageScenario.Run(); + } + } +} diff --git a/test/dotnet-test.UnitTests/GivenThatWeWantToRunTests.cs b/test/dotnet-test.UnitTests/GivenThatWeWantToRunTests.cs new file mode 100644 index 000000000..01da29141 --- /dev/null +++ b/test/dotnet-test.UnitTests/GivenThatWeWantToRunTests.cs @@ -0,0 +1,86 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Diagnostics; +using Microsoft.DotNet.Tools.Test; +using Microsoft.Extensions.Testing.Abstractions; +using Moq; +using Newtonsoft.Json.Linq; +using Xunit; + +namespace Microsoft.Dotnet.Tools.Test.Tests +{ + public class GivenThatWeWantToRunTests + { + [Fact] + public void Dotnet_test_handles_and_sends_all_the_right_messages() + { + var dotnetTestMessageScenario = new DotnetTestMessageScenario(); + + dotnetTestMessageScenario.TestRunnerMock + .Setup(t => t.GetProcessStartInfo()) + .Returns(new ProcessStartInfo()) + .Verifiable(); + + dotnetTestMessageScenario.AdapterChannelMock + .Setup(a => a.Send(It.Is(m => m.MessageType == TestMessageTypes.VersionCheck))) + .Callback(() => dotnetTestMessageScenario.AdapterChannelMock.Raise( + r => r.MessageReceived += null, + dotnetTestMessageScenario.DotnetTestUnderTest, + new Message + { + MessageType = TestMessageTypes.TestExecutionGetTestRunnerProcessStartInfo + })) + .Verifiable(); + + dotnetTestMessageScenario.AdapterChannelMock + .Setup(a => a.Send( + It.Is(m => m.MessageType == TestMessageTypes.TestExecutionTestRunnerProcessStartInfo))) + .Callback(() => dotnetTestMessageScenario.TestRunnerChannelMock.Raise( + t => t.MessageReceived += null, + dotnetTestMessageScenario.DotnetTestUnderTest, + new Message + { + MessageType = TestMessageTypes.TestRunnerTestStarted + })) + .Verifiable(); + + dotnetTestMessageScenario.AdapterChannelMock + .Setup(a => a.Send( + It.Is(m => m.MessageType == TestMessageTypes.TestExecutionStarted))) + .Callback(() => dotnetTestMessageScenario.TestRunnerChannelMock.Raise( + t => t.MessageReceived += null, + dotnetTestMessageScenario.DotnetTestUnderTest, + new Message + { + MessageType = TestMessageTypes.TestRunnerTestResult + })) + .Verifiable(); + + dotnetTestMessageScenario.AdapterChannelMock + .Setup(a => a.Send( + It.Is(m => m.MessageType == TestMessageTypes.TestExecutionTestResult))) + .Callback(() => dotnetTestMessageScenario.TestRunnerChannelMock.Raise( + t => t.MessageReceived += null, + dotnetTestMessageScenario.DotnetTestUnderTest, + new Message + { + MessageType = TestMessageTypes.TestRunnerTestCompleted + })) + .Verifiable(); + + dotnetTestMessageScenario.AdapterChannelMock + .Setup(a => a.Send(It.Is(m => m.MessageType == TestMessageTypes.TestExecutionCompleted))) + .Callback(() => dotnetTestMessageScenario.AdapterChannelMock.Raise( + r => r.MessageReceived += null, + dotnetTestMessageScenario.DotnetTestUnderTest, + new Message + { + MessageType = TestMessageTypes.TestSessionTerminate + })) + .Verifiable(); + + dotnetTestMessageScenario.Run(); + } + } +} diff --git a/test/dotnet-test.UnitTests/dotnet-test.UnitTests.xproj b/test/dotnet-test.UnitTests/dotnet-test.UnitTests.xproj new file mode 100644 index 000000000..ceb3b9bdd --- /dev/null +++ b/test/dotnet-test.UnitTests/dotnet-test.UnitTests.xproj @@ -0,0 +1,18 @@ + + + + 14.0.24720 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + 857274ac-e741-4266-a7fd-14dee0c1cc96 + Microsoft.Dotnet.Tools.Test.Tests + ..\..\artifacts\obj\$(MSBuildProjectName) + ..\..\artifacts\bin\$(MSBuildProjectName)\ + + + 2.0 + + + \ No newline at end of file diff --git a/test/dotnet-test.UnitTests/project.json b/test/dotnet-test.UnitTests/project.json new file mode 100644 index 000000000..1183f75f6 --- /dev/null +++ b/test/dotnet-test.UnitTests/project.json @@ -0,0 +1,23 @@ +{ + "version": "1.0.0-*", + + "dependencies": { + "Newtonsoft.Json": "7.0.1", + "NETStandard.Library": "1.0.0-rc2-23811", + + "dotnet": { "target": "project" }, + + "xunit": "2.1.0", + "dotnet-test-xunit": "1.0.0-dev-48273-16", + "moq.netcore": "4.4.0-beta8", + "FluentAssertions": "4.2.2" + }, + + "frameworks": { + "dnxcore50": { + "imports": "portable-net45+win8" + } + }, + + "testRunner": "xunit" +}