diff --git a/Microsoft.DotNet.Cli.sln b/Microsoft.DotNet.Cli.sln index 7840b9296..bcc98ea83 100644 --- a/Microsoft.DotNet.Cli.sln +++ b/Microsoft.DotNet.Cli.sln @@ -172,6 +172,8 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "dotnet-msbuild.Tests", "tes EndProject Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "dotnet-publish3.Tests", "test\dotnet-publish3.Tests\dotnet-publish3.Tests.xproj", "{FBE4F1D6-BA4C-46D9-8286-4EE4B032D01E}" EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "dotnet-test3.Tests", "test\dotnet-test3.Tests\dotnet-test3.Tests.xproj", "{90236A80-4B84-41D3-A161-AA20709776B1}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -1000,6 +1002,22 @@ Global {FBE4F1D6-BA4C-46D9-8286-4EE4B032D01E}.RelWithDebInfo|Any CPU.Build.0 = Release|Any CPU {FBE4F1D6-BA4C-46D9-8286-4EE4B032D01E}.RelWithDebInfo|x64.ActiveCfg = Release|Any CPU {FBE4F1D6-BA4C-46D9-8286-4EE4B032D01E}.RelWithDebInfo|x64.Build.0 = Release|Any CPU + {90236A80-4B84-41D3-A161-AA20709776B1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {90236A80-4B84-41D3-A161-AA20709776B1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {90236A80-4B84-41D3-A161-AA20709776B1}.Debug|x64.ActiveCfg = Debug|Any CPU + {90236A80-4B84-41D3-A161-AA20709776B1}.Debug|x64.Build.0 = Debug|Any CPU + {90236A80-4B84-41D3-A161-AA20709776B1}.MinSizeRel|Any CPU.ActiveCfg = Debug|Any CPU + {90236A80-4B84-41D3-A161-AA20709776B1}.MinSizeRel|Any CPU.Build.0 = Debug|Any CPU + {90236A80-4B84-41D3-A161-AA20709776B1}.MinSizeRel|x64.ActiveCfg = Debug|Any CPU + {90236A80-4B84-41D3-A161-AA20709776B1}.MinSizeRel|x64.Build.0 = Debug|Any CPU + {90236A80-4B84-41D3-A161-AA20709776B1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {90236A80-4B84-41D3-A161-AA20709776B1}.Release|Any CPU.Build.0 = Release|Any CPU + {90236A80-4B84-41D3-A161-AA20709776B1}.Release|x64.ActiveCfg = Release|Any CPU + {90236A80-4B84-41D3-A161-AA20709776B1}.Release|x64.Build.0 = Release|Any CPU + {90236A80-4B84-41D3-A161-AA20709776B1}.RelWithDebInfo|Any CPU.ActiveCfg = Release|Any CPU + {90236A80-4B84-41D3-A161-AA20709776B1}.RelWithDebInfo|Any CPU.Build.0 = Release|Any CPU + {90236A80-4B84-41D3-A161-AA20709776B1}.RelWithDebInfo|x64.ActiveCfg = Release|Any CPU + {90236A80-4B84-41D3-A161-AA20709776B1}.RelWithDebInfo|x64.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1064,5 +1082,6 @@ Global {9F5AE280-A040-4160-9799-6504D907742D} = {17735A9D-BFD9-4585-A7CB-3208CA6EA8A7} {2FFCBDF0-BA36-4393-8DDB-1AE04AEA3CD6} = {17735A9D-BFD9-4585-A7CB-3208CA6EA8A7} {FBE4F1D6-BA4C-46D9-8286-4EE4B032D01E} = {17735A9D-BFD9-4585-A7CB-3208CA6EA8A7} + {90236A80-4B84-41D3-A161-AA20709776B1} = {17735A9D-BFD9-4585-A7CB-3208CA6EA8A7} EndGlobalSection EndGlobal diff --git a/resources/MSBuildImports/15.0/Microsoft.Common.targets/ImportAfter/Microsoft.NuGet.ImportAfter.targets b/resources/MSBuildImports/15.0/Microsoft.Common.targets/ImportAfter/Microsoft.NuGet.ImportAfter.targets index f88dc4adf..22d290e9a 100644 --- a/resources/MSBuildImports/15.0/Microsoft.Common.targets/ImportAfter/Microsoft.NuGet.ImportAfter.targets +++ b/resources/MSBuildImports/15.0/Microsoft.Common.targets/ImportAfter/Microsoft.NuGet.ImportAfter.targets @@ -20,4 +20,10 @@ Copyright (c) .NET Foundation. All rights reserved. $(MSBuildExtensionsPath)\NuGet.targets + + + + $(MSBuildExtensionsPath)\Microsoft.TestPlatform.targets + + diff --git a/src/dotnet/Program.cs b/src/dotnet/Program.cs index c31ea5ae5..254d69153 100644 --- a/src/dotnet/Program.cs +++ b/src/dotnet/Program.cs @@ -27,6 +27,7 @@ using Microsoft.DotNet.Tools.Restore3; using Microsoft.DotNet.Tools.Run; using Microsoft.DotNet.Tools.Test; using Microsoft.DotNet.Tools.VSTest; +using Microsoft.DotNet.Tools.Test3; using NuGet.Frameworks; namespace Microsoft.DotNet.Cli @@ -51,6 +52,7 @@ namespace Microsoft.DotNet.Cli ["restore3"] = Restore3Command.Run, ["publish3"] = Publish3Command.Run, ["vstest"] = VSTestCommand.Run, + ["test3"] = Test3Command.Run, ["pack3"] = Pack3Command.Run, ["migrate"] = MigrateCommand.Run, ["projectmodel-server"] = ProjectModelServerCommand.Run, diff --git a/src/dotnet/commands/dotnet-test3/Program.cs b/src/dotnet/commands/dotnet-test3/Program.cs new file mode 100644 index 000000000..146f60c5c --- /dev/null +++ b/src/dotnet/commands/dotnet-test3/Program.cs @@ -0,0 +1,247 @@ +// 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.DotNet.Cli.CommandLine; +using Microsoft.DotNet.Cli.Utils; +using Microsoft.DotNet.Tools.MSBuild; +using System.IO; +using System.Text.RegularExpressions; + +namespace Microsoft.DotNet.Tools.Test3 +{ + public class Test3Command + { + public static int Run(string[] args) + { + DebugHelper.HandleDebugSwitch(ref args); + + var cmd = new CommandLineApplication(throwOnUnexpectedArg: false) + { + Name = "dotnet test3", + FullName = ".NET Test Driver", + Description = "Test Driver for the .NET Platform" + }; + + cmd.HelpOption("-h|--help"); + + var argRoot = cmd.Argument( + "", + "The project to test, defaults to the current directory.", + multipleValues: false); + + var settingOption = cmd.Option( + "--settings ", + "Settings to use when running tests." + Environment.NewLine, + CommandOptionType.SingleValue); + + var testsOption = cmd.Option( + "--tests ", + @"Run tests with names that match the provided values. To provide multiple + values, separate them by commas. + Examples: --tests:TestMethod1 + --tests:TestMethod1,testMethod2" + Environment.NewLine, + CommandOptionType.SingleValue); + + var testAdapterPathOption = cmd.Option( + "--testAdapterPath", + @"This makes vstest.console.exe process use custom test adapters + from a given path (if any) in the test run. + Example --testAdapterPath:" + Environment.NewLine, + CommandOptionType.SingleValue); + + var platformOption = cmd.Option( + "--platform ", + @"Target platform architecture to be used for test execution. + Valid values are x86, x64 and ARM." + Environment.NewLine, + CommandOptionType.SingleValue); + + var frameworkOption = cmd.Option( + "--framework ", + @"Target .Net Framework version to be used for test execution. + Valid values are "".NETFramework, Version = v4.6"", "".NETCoreApp, Version = v1.0"" etc. + Other supported values are Framework35, Framework40 and Framework45" + Environment.NewLine, + CommandOptionType.SingleValue); + + var testCaseFilterOption = cmd.Option( + "--testCaseFilter ", + @"Run tests that match the given expression. + is of the format Operator[|&] + where Operator is one of =, != or ~ (Operator ~ has 'contains' + semantics and is applicable for string properties like DisplayName). + Parenthesis () can be used to group sub-expressions. + Examples: --testCaseFilter:""Priority = 1"" + --testCaseFilter:""(FullyQualifiedName~Nightly | Name = MyTestMethod)""" + Environment.NewLine, + CommandOptionType.SingleValue); + + // TODO tfs publisher text + var loggerOption = cmd.Option( + "--logger ", + @"Specify a logger for test results. For example, to log results into a + Visual Studio Test Results File(TRX) use --logger:trx" + Environment.NewLine, + CommandOptionType.MultipleValue); + + var listTestsOption = cmd.Option( + "-lt|--listTests", + @"Lists discovered tests" + Environment.NewLine, + CommandOptionType.NoValue); + + var parentProcessIdOption = cmd.Option( + "--parentProcessId ", + @"Process Id of the Parent Process responsible for launching current process." + Environment.NewLine, + CommandOptionType.SingleValue); + + var portOption = cmd.Option( + "--port ", + @"The Port for socket connection and receiving the event messages." + Environment.NewLine, + CommandOptionType.SingleValue); + + cmd.OnExecute(() => + { + var msbuildArgs = new List() + { + "/t:VSTest" + }; + + msbuildArgs.Add("/verbosity:quiet"); + msbuildArgs.Add("/nologo"); + + if (settingOption.HasValue()) + { + msbuildArgs.Add($"/p:VSTestSetting={settingOption.Value()}"); + } + + if (testsOption.HasValue()) + { + msbuildArgs.Add($"/p:VSTestTests={testsOption.Value()}"); + } + + if (testAdapterPathOption.HasValue()) + { + msbuildArgs.Add($"/p:VSTestTestAdapterPath={testAdapterPathOption.Value()}"); + } + + if (platformOption.HasValue()) + { + msbuildArgs.Add($"/p:VSTestPlatform={platformOption.Value()}"); + } + + if (frameworkOption.HasValue()) + { + msbuildArgs.Add($"/p:VSTestFramework={frameworkOption.Value()}"); + } + + if (testCaseFilterOption.HasValue()) + { + msbuildArgs.Add($"/p:VSTestTestCaseFilter={testCaseFilterOption.Value()}"); + } + + if (loggerOption.HasValue()) + { + msbuildArgs.Add($"/p:VSTestLogger={string.Join(";", loggerOption.Values)}"); + } + + if (listTestsOption.HasValue()) + { + msbuildArgs.Add($"/p:VSTestListTests=true"); + } + + if (parentProcessIdOption.HasValue()) + { + msbuildArgs.Add($"/p:VSTestParentProcessId={parentProcessIdOption.Value()}"); + } + + if (portOption.HasValue()) + { + msbuildArgs.Add($"/p:VSTestPort={portOption.Value()}"); + } + + string defaultproject = GetSingleTestProjectToRunTestIfNotProvided(argRoot.Value, cmd.RemainingArguments); + + if(!string.IsNullOrEmpty(defaultproject)) + { + msbuildArgs.Add(defaultproject); + } + + msbuildArgs.Add(argRoot.Value); + + // Add remaining arguments that the parser did not understand, + msbuildArgs.AddRange(cmd.RemainingArguments); + + return new MSBuildForwardingApp(msbuildArgs).Execute(); + }); + + return cmd.Execute(args); + } + + private static string GetSingleTestProjectToRunTestIfNotProvided(string args, List remainingArguments) + { + string result = string.Empty; + int projectFound = NumberOfTestProjectInRemainingArgs(remainingArguments) + NumberOfTestProjectArgsRoot(args); + + if (projectFound > 1) + { + throw new GracefulException( + $"Specify a single project file to run tests from."); + } + else if (projectFound == 0) + { + result = GetDefaultTestProject(); + } + + return result; + } + + private static int NumberOfTestProjectArgsRoot(string args) + { + Regex pattern = new Regex(@"^.*\..*proj$"); + + if (!string.IsNullOrEmpty(args)) + { + return pattern.IsMatch(args) ? 1 : 0; + } + + return 0; + } + + private static int NumberOfTestProjectInRemainingArgs(List remainingArguments) + { + int count = 0; + if (remainingArguments.Count != 0) + { + Regex pattern = new Regex(@"^.*\..*proj$"); + + foreach (var x in remainingArguments) + { + if (pattern.IsMatch(x)) + { + count++; + } + } + } + + return count; + } + + private static string GetDefaultTestProject() + { + string directory = Directory.GetCurrentDirectory(); + string[] projectFiles = Directory.GetFiles(directory, "*.*proj"); + + if (projectFiles.Length == 0) + { + throw new GracefulException( + $"Couldn't find a project to run test from. Ensure a project exists in {directory}." + Environment.NewLine + + "Or pass the path to the project"); + } + else if (projectFiles.Length > 1) + { + throw new GracefulException( + $"Specify which project file to use because this '{directory}' contains more than one project file."); + } + + return projectFiles[0]; + } + } +} diff --git a/test/Microsoft.DotNet.Tools.Tests.Utilities/Commands/Test3Command.cs b/test/Microsoft.DotNet.Tools.Tests.Utilities/Commands/Test3Command.cs new file mode 100644 index 000000000..985bc2061 --- /dev/null +++ b/test/Microsoft.DotNet.Tools.Tests.Utilities/Commands/Test3Command.cs @@ -0,0 +1,27 @@ +// 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.Utilities +{ + public sealed class Test3Command : TestCommand + { + public Test3Command() + : base("dotnet") + { + } + + public override CommandResult Execute(string args = "") + { + args = $"test3 {args}"; + return base.Execute(args); + } + + public override CommandResult ExecuteWithCapturedOutput(string args = "") + { + args = $"test3 {args}"; + return base.ExecuteWithCapturedOutput(args); + } + } +} diff --git a/test/dotnet-test3.Tests/Test3Tests.cs b/test/dotnet-test3.Tests/Test3Tests.cs new file mode 100644 index 000000000..97adbbc49 --- /dev/null +++ b/test/dotnet-test3.Tests/Test3Tests.cs @@ -0,0 +1,39 @@ +// 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.Utilities; +using Xunit; +using FluentAssertions; +using Microsoft.DotNet.TestFramework; +using Microsoft.DotNet.Cli.Utils; + +namespace Microsoft.DotNet.Cli.Test3.Tests +{ + public class Test3Tests : TestBase + { + [Fact] + public void TestsFromAGivenProjectShouldRunWithExpectedOutput() + { + // Copy DotNetCoreTestProject project in output directory of project dotnet-vstest.Tests + string testAppName = "VSTestDotNetCoreProject"; + TestInstance testInstance = TestAssetsManager.CreateTestInstance(testAppName); + + string testProjectDirectory = testInstance.TestRoot; + + // Restore project VSTestDotNetCoreProject + new Restore3Command() + .WithWorkingDirectory(testProjectDirectory) + .Execute() + .Should() + .Pass(); + + // Call test3 + CommandResult result = new Test3Command().WithWorkingDirectory(testProjectDirectory).ExecuteWithCapturedOutput("/p:TargetFramework=netcoreapp1.0"); + + // Verify + result.StdOut.Should().Contain("Total tests: 2. Passed: 1. Failed: 1. Skipped: 0."); + result.StdOut.Should().Contain("Passed TestNamespace.VSTestTests.VSTestPassTest"); + result.StdOut.Should().Contain("Failed TestNamespace.VSTestTests.VSTestFailTest"); + } + } +} \ No newline at end of file diff --git a/test/dotnet-test3.Tests/dotnet-test3.Tests.xproj b/test/dotnet-test3.Tests/dotnet-test3.Tests.xproj new file mode 100644 index 000000000..6417b9f10 --- /dev/null +++ b/test/dotnet-test3.Tests/dotnet-test3.Tests.xproj @@ -0,0 +1,21 @@ + + + + 14.0.23107 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + 90236a80-4b84-41d3-a161-aa20709776b1 + Microsoft.DotNet.Cli.Test3.Tests + ..\..\artifacts\obj\$(MSBuildProjectName) + ..\..\artifacts\bin + + + 2.0 + + + + + + \ No newline at end of file diff --git a/test/dotnet-test3.Tests/project.json b/test/dotnet-test3.Tests/project.json new file mode 100644 index 000000000..7b59006f9 --- /dev/null +++ b/test/dotnet-test3.Tests/project.json @@ -0,0 +1,24 @@ +{ + "version": "1.0.0-*", + "dependencies": { + "Microsoft.NETCore.App": { + "type": "platform", + "version": "1.0.0" + }, + "System.Runtime.Serialization.Primitives": "4.1.1", + "Microsoft.DotNet.Tools.Tests.Utilities": { + "target": "project" + }, + "xunit": "2.2.0-beta3-build3330", + "dotnet-test-xunit": "1.0.0-rc2-330423-54" + }, + "frameworks": { + "netcoreapp1.0": { + "imports": [ + "dotnet5.4", + "portable-net451+win8" + ] + } + }, + "testRunner": "xunit" +} \ No newline at end of file