From 76303370741c1e77c4b6802334893aae96073f79 Mon Sep 17 00:00:00 2001 From: Livar Cunha Date: Fri, 12 Feb 2016 18:22:35 -0800 Subject: [PATCH] Adding unit tests for the state machine of dotnet test. Starting the implementation of a state machine in dotnet test. Right now we only handle the TestSession:Terminate message. Adding a message handler for the version check message. Also introduced an IDotnetTest that handles state and handlers (the state machine). Adding the test discover start message handler and introducing a test runner. Added the handler for the GetTestRunnerProcessInfo message. Also, modified dotnet test to have separate setter for the special message handlers for terminate and unknown messages and added a separate method to add new reporting channels to DotnetTest, so that it can handle the new listener for the test runner. Added the test runner test discovery handlers. Added handlers to deal with the test execution itself. Updated dotnet-test program to use the message handlers during design time. Added a test for the whole discover tests message flow. Added a test for the run tests full message exchange. --- Microsoft.DotNet.Cli.sln | 77 +++----- global.json | 5 +- .../CommandTestRunnerExtensions.cs | 16 ++ src/dotnet/commands/dotnet-test/DotnetTest.cs | 106 +++++++++++ .../dotnet-test/DotnetTestExtensions.cs | 61 +++++++ .../commands/dotnet-test/DotnetTestState.cs | 18 ++ .../commands/dotnet-test/IDotnetTest.cs | 24 +++ .../commands/dotnet-test/IReportingChannel.cs | 21 +++ .../dotnet-test/IReportingChannelFactory.cs | 12 ++ .../dotnet-test/ITestMessagesCollection.cs | 17 ++ ...estRunnerProcessStartInfoMessageHandler.cs | 62 +++++++ .../IDotnetTestMessageHandler.cs | 12 ++ .../TestDiscoveryStartMessageHandler.cs | 75 ++++++++ .../MessageHandlers/TestMessageTypes.cs | 23 +++ .../TestRunnerResultMessageHandler.cs | 47 +++++ .../TestRunnerTestCompletedMessageHandler.cs | 67 +++++++ .../TestRunnerTestFoundMessageHandler.cs | 21 +++ .../TestRunnerTestResultMessageHandler.cs | 21 +++ .../TestRunnerTestStartedMessageHandler.cs | 21 +++ .../TestSessionTerminateMessageHandler.cs | 30 ++++ .../MessageHandlers/UnknownMessageHandler.cs | 30 ++++ .../VersionCheckMessageHandler.cs | 56 ++++++ src/dotnet/commands/dotnet-test/Program.cs | 168 ++++-------------- .../commands/dotnet-test/ReportingChannel.cs | 39 +--- .../dotnet-test/ReportingChannelFactory.cs | 18 ++ .../dotnet-test/TestMessagesCollection.cs | 73 ++++++++ .../DiscoverTestsArgumentsBuilder.cs | 34 ++++ .../dotnet-test/TestRunners/ITestRunner.cs | 16 ++ .../ITestRunnerArgumentsBuilder.cs | 12 ++ .../TestRunners/ITestRunnerFactory.cs | 12 ++ .../TestRunners/RunTestsArgumentsBuilder.cs | 45 +++++ .../dotnet-test/TestRunners/TestRunner.cs | 59 ++++++ .../TestRunners/TestRunnerFactory.cs | 24 +++ .../TestRunnerOperationFailedException.cs | 20 +++ .../DotnetTestMessageScenario.cs | 76 ++++++++ .../GivenADiscoverTestsArgumentsBuilder.cs | 25 +++ .../GivenADotnetTestApp.cs | 157 ++++++++++++++++ .../GivenARunTestsArgumentsBuilder.cs | 41 +++++ .../GivenATestDiscoveryStartMessageHandler.cs | 152 ++++++++++++++++ ...estRunnerProcessStartInfoMessageHandler.cs | 165 +++++++++++++++++ .../dotnet-test.UnitTests/GivenATestRunner.cs | 106 +++++++++++ ...nATestRunnerTestCompletedMessageHandler.cs | 122 +++++++++++++ ...GivenATestRunnerTestFoundMessageHandler.cs | 84 +++++++++ ...ivenATestRunnerTestResultMessageHandler.cs | 84 +++++++++ ...venATestRunnerTestStartedMessageHandler.cs | 99 +++++++++++ ...ivenATestSessionTerminateMessageHandler.cs | 42 +++++ .../GivenAUnknownMessageHandler.cs | 37 ++++ .../GivenAVersionCheckMessageHandler.cs | 83 +++++++++ .../GivenThatWeWantToDiscoverTests.cs | 67 +++++++ .../GivenThatWeWantToRunTests.cs | 86 +++++++++ .../dotnet-test.UnitTests.xproj | 18 ++ test/dotnet-test.UnitTests/project.json | 23 +++ 52 files changed, 2581 insertions(+), 228 deletions(-) create mode 100644 src/dotnet/commands/dotnet-test/CommandTestRunnerExtensions.cs create mode 100644 src/dotnet/commands/dotnet-test/DotnetTest.cs create mode 100644 src/dotnet/commands/dotnet-test/DotnetTestExtensions.cs create mode 100644 src/dotnet/commands/dotnet-test/DotnetTestState.cs create mode 100644 src/dotnet/commands/dotnet-test/IDotnetTest.cs create mode 100644 src/dotnet/commands/dotnet-test/IReportingChannel.cs create mode 100644 src/dotnet/commands/dotnet-test/IReportingChannelFactory.cs create mode 100644 src/dotnet/commands/dotnet-test/ITestMessagesCollection.cs create mode 100644 src/dotnet/commands/dotnet-test/MessageHandlers/GetTestRunnerProcessStartInfoMessageHandler.cs create mode 100644 src/dotnet/commands/dotnet-test/MessageHandlers/IDotnetTestMessageHandler.cs create mode 100644 src/dotnet/commands/dotnet-test/MessageHandlers/TestDiscoveryStartMessageHandler.cs create mode 100644 src/dotnet/commands/dotnet-test/MessageHandlers/TestMessageTypes.cs create mode 100644 src/dotnet/commands/dotnet-test/MessageHandlers/TestRunnerResultMessageHandler.cs create mode 100644 src/dotnet/commands/dotnet-test/MessageHandlers/TestRunnerTestCompletedMessageHandler.cs create mode 100644 src/dotnet/commands/dotnet-test/MessageHandlers/TestRunnerTestFoundMessageHandler.cs create mode 100644 src/dotnet/commands/dotnet-test/MessageHandlers/TestRunnerTestResultMessageHandler.cs create mode 100644 src/dotnet/commands/dotnet-test/MessageHandlers/TestRunnerTestStartedMessageHandler.cs create mode 100644 src/dotnet/commands/dotnet-test/MessageHandlers/TestSessionTerminateMessageHandler.cs create mode 100644 src/dotnet/commands/dotnet-test/MessageHandlers/UnknownMessageHandler.cs create mode 100644 src/dotnet/commands/dotnet-test/MessageHandlers/VersionCheckMessageHandler.cs create mode 100644 src/dotnet/commands/dotnet-test/ReportingChannelFactory.cs create mode 100644 src/dotnet/commands/dotnet-test/TestMessagesCollection.cs create mode 100644 src/dotnet/commands/dotnet-test/TestRunners/DiscoverTestsArgumentsBuilder.cs create mode 100644 src/dotnet/commands/dotnet-test/TestRunners/ITestRunner.cs create mode 100644 src/dotnet/commands/dotnet-test/TestRunners/ITestRunnerArgumentsBuilder.cs create mode 100644 src/dotnet/commands/dotnet-test/TestRunners/ITestRunnerFactory.cs create mode 100644 src/dotnet/commands/dotnet-test/TestRunners/RunTestsArgumentsBuilder.cs create mode 100644 src/dotnet/commands/dotnet-test/TestRunners/TestRunner.cs create mode 100644 src/dotnet/commands/dotnet-test/TestRunners/TestRunnerFactory.cs create mode 100644 src/dotnet/commands/dotnet-test/TestRunners/TestRunnerOperationFailedException.cs create mode 100644 test/dotnet-test.UnitTests/DotnetTestMessageScenario.cs create mode 100644 test/dotnet-test.UnitTests/GivenADiscoverTestsArgumentsBuilder.cs create mode 100644 test/dotnet-test.UnitTests/GivenADotnetTestApp.cs create mode 100644 test/dotnet-test.UnitTests/GivenARunTestsArgumentsBuilder.cs create mode 100644 test/dotnet-test.UnitTests/GivenATestDiscoveryStartMessageHandler.cs create mode 100644 test/dotnet-test.UnitTests/GivenATestExecutionGetTestRunnerProcessStartInfoMessageHandler.cs create mode 100644 test/dotnet-test.UnitTests/GivenATestRunner.cs create mode 100644 test/dotnet-test.UnitTests/GivenATestRunnerTestCompletedMessageHandler.cs create mode 100644 test/dotnet-test.UnitTests/GivenATestRunnerTestFoundMessageHandler.cs create mode 100644 test/dotnet-test.UnitTests/GivenATestRunnerTestResultMessageHandler.cs create mode 100644 test/dotnet-test.UnitTests/GivenATestRunnerTestStartedMessageHandler.cs create mode 100644 test/dotnet-test.UnitTests/GivenATestSessionTerminateMessageHandler.cs create mode 100644 test/dotnet-test.UnitTests/GivenAUnknownMessageHandler.cs create mode 100644 test/dotnet-test.UnitTests/GivenAVersionCheckMessageHandler.cs create mode 100644 test/dotnet-test.UnitTests/GivenThatWeWantToDiscoverTests.cs create mode 100644 test/dotnet-test.UnitTests/GivenThatWeWantToRunTests.cs create mode 100644 test/dotnet-test.UnitTests/dotnet-test.UnitTests.xproj create mode 100644 test/dotnet-test.UnitTests/project.json 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" +}