Adding a separate runner for project.json and for assembly.

This commit is contained in:
Livar Cunha 2016-08-24 15:24:30 -07:00
parent dddfb6bb45
commit 7e556e37d2
14 changed files with 320 additions and 199 deletions

1
.gitignore vendored
View file

@ -151,6 +151,7 @@ _ReSharper*/
*.DotSettings.user
.idea/
*.iml
*.DotSettings
# JustCode is a .NET coding add-in
.JustCode

View file

@ -0,0 +1,28 @@
// 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.Cli.Utils;
namespace Microsoft.DotNet.Tools.Test
{
public class AssemblyTestRunner : IDotnetTestRunner
{
private readonly Func<ICommandFactory, string, IDotnetTestRunner> _nextRunner;
public AssemblyTestRunner(Func<ICommandFactory, string, IDotnetTestRunner> nextRunner)
{
_nextRunner = nextRunner;
}
public int RunTests(DotnetTestParams dotnetTestParams)
{
var commandFactory =
new CommandFactory();
var assemblyUnderTest = dotnetTestParams.ProjectOrAssemblyPath;
return _nextRunner(commandFactory, assemblyUnderTest).RunTests(dotnetTestParams);
}
}
}

View file

@ -3,46 +3,53 @@
using System.Collections.Generic;
using Microsoft.DotNet.Cli.Utils;
using Microsoft.DotNet.ProjectModel;
using NuGet.Frameworks;
namespace Microsoft.DotNet.Tools.Test
{
public class ConsoleTestRunner : BaseDotnetTestRunner
public class ConsoleTestRunner : IDotnetTestRunner
{
internal override int DoRunTests(ProjectContext projectContext, DotnetTestParams dotnetTestParams)
{
var commandFactory =
new ProjectDependenciesCommandFactory(
projectContext.TargetFramework,
dotnetTestParams.Config,
dotnetTestParams.Output,
dotnetTestParams.BuildBasePath,
projectContext.ProjectDirectory);
private readonly ITestRunnerResolver _testRunnerResolver;
return commandFactory.Create(
GetCommandName(projectContext.ProjectFile.TestRunner),
GetCommandArgs(projectContext, dotnetTestParams),
projectContext.TargetFramework,
private readonly ICommandFactory _commandFactory;
private readonly string _assemblyUnderTest;
private readonly NuGetFramework _framework;
public ConsoleTestRunner(
ITestRunnerResolver testRunnerResolver,
ICommandFactory commandFactory,
string assemblyUnderTest,
NuGetFramework framework = null)
{
_testRunnerResolver = testRunnerResolver;
_commandFactory = commandFactory;
_assemblyUnderTest = assemblyUnderTest;
_framework = framework;
}
public int RunTests(DotnetTestParams dotnetTestParams)
{
return _commandFactory.Create(
_testRunnerResolver.ResolveTestRunner(),
GetCommandArgs(dotnetTestParams),
_framework,
dotnetTestParams.Config)
.Execute()
.ExitCode;
}
private IEnumerable<string> GetCommandArgs(ProjectContext projectContext, DotnetTestParams dotnetTestParams)
private IEnumerable<string> GetCommandArgs(DotnetTestParams dotnetTestParams)
{
var commandArgs = new List<string>
{
new AssemblyUnderTest(projectContext, dotnetTestParams).Path
_assemblyUnderTest
};
commandArgs.AddRange(dotnetTestParams.RemainingArguments);
return commandArgs;
}
private static string GetCommandName(string testRunner)
{
return $"dotnet-test-{testRunner}";
}
}
}

View file

@ -3,44 +3,49 @@
using System;
using Microsoft.DotNet.Cli.Utils;
using Microsoft.DotNet.ProjectModel;
namespace Microsoft.DotNet.Tools.Test
{
public class DesignTimeRunner : BaseDotnetTestRunner
public class DesignTimeRunner : IDotnetTestRunner
{
internal override int DoRunTests(ProjectContext projectContext, DotnetTestParams dotnetTestParams)
private readonly ITestRunnerResolver _testRunnerResolver;
private readonly ICommandFactory _commandFactory;
private readonly string _assemblyUnderTest;
public DesignTimeRunner(
ITestRunnerResolver testRunnerResolver,
ICommandFactory commandFactory,
string assemblyUnderTest)
{
_testRunnerResolver = testRunnerResolver;
_commandFactory = commandFactory;
_assemblyUnderTest = assemblyUnderTest;
}
public int RunTests(DotnetTestParams dotnetTestParams)
{
Console.WriteLine("Listening on port {0}", dotnetTestParams.Port.Value);
HandleDesignTimeMessages(projectContext, dotnetTestParams);
HandleDesignTimeMessages(dotnetTestParams);
return 0;
}
private static void HandleDesignTimeMessages(
ProjectContext projectContext,
DotnetTestParams dotnetTestParams)
private void HandleDesignTimeMessages(DotnetTestParams dotnetTestParams)
{
var reportingChannelFactory = new ReportingChannelFactory();
var adapterChannel = reportingChannelFactory.CreateAdapterChannel(dotnetTestParams.Port.Value);
try
{
var pathToAssemblyUnderTest = new AssemblyUnderTest(projectContext, dotnetTestParams).Path;
var pathToAssemblyUnderTest = _assemblyUnderTest;
var messages = new TestMessagesCollection();
using (var dotnetTest = new DotnetTest(messages, pathToAssemblyUnderTest))
{
var commandFactory =
new ProjectDependenciesCommandFactory(
projectContext.TargetFramework,
dotnetTestParams.Config,
dotnetTestParams.Output,
dotnetTestParams.BuildBasePath,
projectContext.ProjectDirectory);
var testRunnerFactory =
new TestRunnerFactory(GetCommandName(projectContext.ProjectFile.TestRunner), commandFactory);
new TestRunnerFactory(_testRunnerResolver.ResolveTestRunner(), _commandFactory);
dotnetTest
.AddNonSpecificMessageHandlers(messages, adapterChannel)
@ -60,10 +65,5 @@ namespace Microsoft.DotNet.Tools.Test
adapterChannel.SendError(ex);
}
}
private static string GetCommandName(string testRunner)
{
return $"dotnet-test-{testRunner}";
}
}
}

View file

@ -56,6 +56,8 @@ namespace Microsoft.DotNet.Tools.Test
public bool HasTestRunner => !string.IsNullOrWhiteSpace(TestRunner);
public bool IsTestingAssembly => ProjectOrAssemblyPath.EndsWith(".dll");
public DotnetTestParams()
{
_app = new CommandLineApplication(false)
@ -108,7 +110,7 @@ namespace Microsoft.DotNet.Tools.Test
if (_testRunner.HasValue())
{
if (!IsAssembly(ProjectOrAssemblyPath))
if (!IsTestingAssembly)
{
throw new InvalidOperationException("You can only specify a test runner with a dll.");
}
@ -138,11 +140,6 @@ namespace Microsoft.DotNet.Tools.Test
_app.Execute(args);
}
private bool IsAssembly(string projectOrAssemblyPath)
{
return projectOrAssemblyPath.EndsWith(".dll");
}
private void AddDotnetTestParameters()
{
_app.HelpOption("-?|-h|--help");

View file

@ -1,19 +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 System;
using Microsoft.DotNet.Cli.Utils;
using NuGet.Frameworks;
namespace Microsoft.DotNet.Tools.Test
{
public class DotnetTestRunnerFactory : IDotnetTestRunnerFactory
{
public IDotnetTestRunner Create(int? port)
{
IDotnetTestRunner dotnetTestRunner = new ConsoleTestRunner();
if (port.HasValue)
{
dotnetTestRunner = new DesignTimeRunner();
}
private readonly DotnetTestRunnerResolverFactory _dotnetTestRunnerResolverFactory;
return dotnetTestRunner;
public DotnetTestRunnerFactory(DotnetTestRunnerResolverFactory dotnetTestRunnerResolverFactory)
{
_dotnetTestRunnerResolverFactory = dotnetTestRunnerResolverFactory;
}
public IDotnetTestRunner Create(DotnetTestParams dotnetTestParams)
{
Func<ICommandFactory, string, NuGetFramework, IDotnetTestRunner> nextTestRunner =
(commandFactory, assemblyUnderTest, framework) =>
{
var dotnetTestRunnerResolver = _dotnetTestRunnerResolverFactory.Create(dotnetTestParams);
IDotnetTestRunner testRunner =
new ConsoleTestRunner(dotnetTestRunnerResolver, commandFactory, assemblyUnderTest, framework);
if (dotnetTestParams.Port.HasValue)
{
testRunner = new DesignTimeRunner(dotnetTestRunnerResolver, commandFactory, assemblyUnderTest);
}
return testRunner;
};
return dotnetTestParams.IsTestingAssembly
? CreateTestRunnerForAssembly(nextTestRunner)
: CreateTestRunnerForProjectJson(nextTestRunner);
}
private static IDotnetTestRunner CreateTestRunnerForAssembly(
Func<ICommandFactory, string, NuGetFramework, IDotnetTestRunner> nextTestRunner)
{
Func<ICommandFactory, string, IDotnetTestRunner> nextAssemblyTestRunner =
(commandFactory, assemblyUnderTest) => nextTestRunner(commandFactory, assemblyUnderTest, null);
return new AssemblyTestRunner(nextAssemblyTestRunner);
}
private static IDotnetTestRunner CreateTestRunnerForProjectJson(
Func<ICommandFactory, string, NuGetFramework, IDotnetTestRunner> nextTestRunner)
{
return new ProjectJsonTestRunner(nextTestRunner);
}
}
}

View file

@ -1,13 +1,10 @@
// 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.ProjectModel;
using Microsoft.DotNet.Cli.Utils;
namespace Microsoft.DotNet.Tools.Test
{
public interface IDotnetTestRunner
{
int RunTests(ProjectContext projectContext, DotnetTestParams dotnetTestParams, BuildWorkspace workspace);
int RunTests(DotnetTestParams dotnetTestParams);
}
}

View file

@ -5,6 +5,6 @@ namespace Microsoft.DotNet.Tools.Test
{
public interface IDotnetTestRunnerFactory
{
IDotnetTestRunner Create(int? port);
IDotnetTestRunner Create(DotnetTestParams dotnetTestParams);
}
}

View file

@ -15,6 +15,14 @@ namespace Microsoft.DotNet.Tools.Test
{
private readonly IDotnetTestRunnerFactory _dotnetTestRunnerFactory;
public static int Run(string[] args)
{
var dotnetTestRunnerResolverFactory = new DotnetTestRunnerResolverFactory(new ProjectReader());
var testCommand = new TestCommand(new DotnetTestRunnerFactory(dotnetTestRunnerResolverFactory));
return testCommand.DoRun(args);
}
public TestCommand(IDotnetTestRunnerFactory testRunnerFactory)
{
_dotnetTestRunnerFactory = testRunnerFactory;
@ -41,60 +49,7 @@ namespace Microsoft.DotNet.Tools.Test
RegisterForParentProcessExit(dotnetTestParams.ParentProcessId.Value);
}
var projectPath = GetProjectPath(dotnetTestParams.ProjectOrAssemblyPath);
var runtimeIdentifiers = !string.IsNullOrEmpty(dotnetTestParams.Runtime) ?
new[] { dotnetTestParams.Runtime } :
RuntimeEnvironmentRidExtensions.GetAllCandidateRuntimeIdentifiers();
var exitCode = 0;
// Create a workspace
var workspace = new BuildWorkspace(ProjectReaderSettings.ReadFromEnvironment());
if (dotnetTestParams.Framework != null)
{
var projectContext = workspace.GetProjectContext(projectPath, dotnetTestParams.Framework);
if (projectContext == null)
{
Reporter.Error.WriteLine($"Project '{projectPath}' does not support framework: {dotnetTestParams.UnparsedFramework}");
return 1;
}
projectContext = workspace.GetRuntimeContext(projectContext, runtimeIdentifiers);
exitCode = RunTest(projectContext, dotnetTestParams, workspace);
}
else
{
var summary = new Summary();
var projectContexts = workspace.GetProjectContextCollection(projectPath)
.EnsureValid(projectPath)
.FrameworkOnlyContexts
.Select(c => workspace.GetRuntimeContext(c, runtimeIdentifiers))
.ToList();
// Execute for all TFMs the project targets.
foreach (var projectContext in projectContexts)
{
var result = RunTest(projectContext, dotnetTestParams, workspace);
if (result == 0)
{
summary.Passed++;
}
else
{
summary.Failed++;
if (exitCode == 0)
{
// If tests fail in more than one TFM, we'll have it use the result of the first one
// as the exit code.
exitCode = result;
}
}
}
summary.Print();
}
return exitCode;
return RunTest(dotnetTestParams);
}
catch (InvalidOperationException ex)
{
@ -108,13 +63,6 @@ namespace Microsoft.DotNet.Tools.Test
}
}
public static int Run(string[] args)
{
var testCommand = new TestCommand(new DotnetTestRunnerFactory());
return testCommand.DoRun(args);
}
private static void RegisterForParentProcessExit(int id)
{
var parentProcess = Process.GetProcesses().FirstOrDefault(p => p.Id == id);
@ -142,50 +90,10 @@ namespace Microsoft.DotNet.Tools.Test
}
}
private int RunTest(ProjectContext projectContext, DotnetTestParams dotnetTestParams, BuildWorkspace workspace)
private int RunTest(DotnetTestParams dotnetTestParams)
{
var testRunner = projectContext.ProjectFile.TestRunner;
var dotnetTestRunner = _dotnetTestRunnerFactory.Create(dotnetTestParams.Port);
return dotnetTestRunner.RunTests(projectContext, dotnetTestParams, workspace);
}
private static string GetProjectPath(string projectPath)
{
projectPath = projectPath ?? Directory.GetCurrentDirectory();
if (!projectPath.EndsWith(Project.FileName))
{
projectPath = Path.Combine(projectPath, Project.FileName);
}
if (!File.Exists(projectPath))
{
throw new InvalidOperationException($"{projectPath} does not exist.");
}
return projectPath;
}
private class Summary
{
public int Passed { get; set; }
public int Failed { get; set; }
public int Total => Passed + Failed;
public void Print()
{
var summaryMessage = $"SUMMARY: Total: {Total} targets, Passed: {Passed}, Failed: {Failed}.";
if (Failed > 0)
{
Reporter.Error.WriteLine(summaryMessage.Red());
}
else
{
Reporter.Output.WriteLine(summaryMessage);
}
}
var dotnetTestRunner = _dotnetTestRunnerFactory.Create(dotnetTestParams);
return dotnetTestRunner.RunTests(dotnetTestParams);
}
}
}

View file

@ -2,46 +2,29 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System.Collections.Generic;
using System.Runtime.InteropServices.ComTypes;
using Microsoft.DotNet.Cli.Utils;
using Microsoft.DotNet.ProjectModel;
namespace Microsoft.DotNet.Tools.Test
{
public abstract class BaseDotnetTestRunner : IDotnetTestRunner
public class TestProjectBuilder
{
public int RunTests(ProjectContext projectContext, DotnetTestParams dotnetTestParams, BuildWorkspace workspace)
public int BuildTestProject(ProjectContext projectContext, DotnetTestParams dotnetTestParams)
{
var result = BuildTestProject(projectContext, dotnetTestParams, workspace);
return result == 0 ? DoRunTests(projectContext, dotnetTestParams) : result;
return dotnetTestParams.NoBuild ? 0 : DoBuildTestProject(projectContext, dotnetTestParams);
}
internal abstract int DoRunTests(ProjectContext projectContext, DotnetTestParams dotnetTestParams);
private int BuildTestProject(ProjectContext projectContext, DotnetTestParams dotnetTestParams, BuildWorkspace workspace)
{
if (dotnetTestParams.NoBuild)
{
return 0;
}
return DoBuildTestProject(projectContext, dotnetTestParams, workspace);
}
private int DoBuildTestProject(ProjectContext projectContext, DotnetTestParams dotnetTestParams, BuildWorkspace workspace)
private int DoBuildTestProject(ProjectContext projectContext, DotnetTestParams dotnetTestParams)
{
var strings = new List<string>
{
$"--configuration",
dotnetTestParams.Config,
$"{dotnetTestParams.ProjectOrAssemblyPath}"
$"{dotnetTestParams.ProjectOrAssemblyPath}",
$"--configuration", dotnetTestParams.Config,
"--framework", projectContext.TargetFramework.ToString()
};
// Build the test specifically for the target framework \ rid of the ProjectContext. This avoids building the project
// for tfms that the user did not request.
strings.Add("--framework");
strings.Add(projectContext.TargetFramework.ToString());
// Build the test specifically for the target framework \ rid of the ProjectContext.
// This avoids building the project for tfms that the user did not request.
if (!string.IsNullOrEmpty(dotnetTestParams.BuildBasePath))
{
@ -66,4 +49,4 @@ namespace Microsoft.DotNet.Tools.Test
return result;
}
}
}
}

View file

@ -13,14 +13,14 @@ namespace Microsoft.DotNet.Tools.Test
private readonly IDirectory _directory;
public AssemblyTestRunnerResolver(string directoryOfAssemblyUnderTest) :
this(directoryOfAssemblyUnderTest, FileSystemWrapper.Default.Directory)
public AssemblyTestRunnerResolver(string assemblyUnderTest) :
this(assemblyUnderTest, FileSystemWrapper.Default.Directory)
{
}
internal AssemblyTestRunnerResolver(string directoryOfAssemblyUnderTest, IDirectory directory)
internal AssemblyTestRunnerResolver(string assemblyUnderTest, IDirectory directory)
{
_directoryOfAssemblyUnderTest = Path.GetDirectoryName(directoryOfAssemblyUnderTest);
_directoryOfAssemblyUnderTest = new FileInfo(assemblyUnderTest).Directory.FullName;
_directory = directory;
}

View file

@ -1,6 +1,7 @@
// 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.IO;
using Microsoft.DotNet.ProjectModel;
namespace Microsoft.DotNet.Tools.Test
@ -16,7 +17,7 @@ namespace Microsoft.DotNet.Tools.Test
public ITestRunnerResolver Create(DotnetTestParams dotnetTestParams)
{
var testRunnerResolver = dotnetTestParams.ProjectOrAssemblyPath.EndsWith(".dll") ?
var testRunnerResolver = dotnetTestParams.IsTestingAssembly ?
GetAssemblyTestRunnerResolver(dotnetTestParams) :
GetProjectJsonTestRunnerResolver(dotnetTestParams);

View file

@ -0,0 +1,148 @@
// 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.IO;
using Microsoft.DotNet.Cli.Utils;
using Microsoft.DotNet.InternalAbstractions;
using Microsoft.DotNet.ProjectModel;
using System.Linq;
using NuGet.Frameworks;
namespace Microsoft.DotNet.Tools.Test
{
public class ProjectJsonTestRunner : IDotnetTestRunner
{
private readonly Func<ICommandFactory, string, NuGetFramework, IDotnetTestRunner> _nextRunner;
private readonly TestProjectBuilder _testProjectBuilder;
public ProjectJsonTestRunner(
Func<ICommandFactory, string, NuGetFramework, IDotnetTestRunner> nextRunner)
{
_nextRunner = nextRunner;
_testProjectBuilder = new TestProjectBuilder();
}
public int RunTests(DotnetTestParams dotnetTestParams)
{
var projectPath = GetProjectPath(dotnetTestParams.ProjectOrAssemblyPath);
var runtimeIdentifiers = !string.IsNullOrEmpty(dotnetTestParams.Runtime)
? new[] {dotnetTestParams.Runtime}
: RuntimeEnvironmentRidExtensions.GetAllCandidateRuntimeIdentifiers();
var exitCode = 0;
// Create a workspace
var workspace = new BuildWorkspace(ProjectReaderSettings.ReadFromEnvironment());
if (dotnetTestParams.Framework != null)
{
var projectContext = workspace.GetProjectContext(projectPath, dotnetTestParams.Framework);
if (projectContext == null)
{
Reporter.Error.WriteLine(
$"Project '{projectPath}' does not support framework: {dotnetTestParams.UnparsedFramework}");
return 1;
}
projectContext = workspace.GetRuntimeContext(projectContext, runtimeIdentifiers);
exitCode = RunTests(projectContext, dotnetTestParams);
}
else
{
var summary = new Summary();
var projectContexts = workspace.GetProjectContextCollection(projectPath)
.EnsureValid(projectPath)
.FrameworkOnlyContexts
.Select(c => workspace.GetRuntimeContext(c, runtimeIdentifiers))
.ToList();
// Execute for all TFMs the project targets.
foreach (var projectContext in projectContexts)
{
var result = RunTests(projectContext, dotnetTestParams);
if (result == 0)
{
summary.Passed++;
}
else
{
summary.Failed++;
if (exitCode == 0)
{
// If tests fail in more than one TFM, we'll have it use the result of the first one
// as the exit code.
exitCode = result;
}
}
}
summary.Print();
}
return exitCode;
}
private int RunTests(ProjectContext projectContext, DotnetTestParams dotnetTestParams)
{
var result = _testProjectBuilder.BuildTestProject(projectContext, dotnetTestParams);
if (result == 0)
{
var commandFactory =
new ProjectDependenciesCommandFactory(
projectContext.TargetFramework,
dotnetTestParams.Config,
dotnetTestParams.Output,
dotnetTestParams.BuildBasePath,
projectContext.ProjectDirectory);
var assemblyUnderTest = new AssemblyUnderTest(projectContext, dotnetTestParams);
var framework = projectContext.TargetFramework;
result = _nextRunner(commandFactory, assemblyUnderTest.Path, framework).RunTests(dotnetTestParams);
}
return result;
}
private static string GetProjectPath(string projectPath)
{
projectPath = projectPath ?? Directory.GetCurrentDirectory();
if (!projectPath.EndsWith(Project.FileName))
{
projectPath = Path.Combine(projectPath, Project.FileName);
}
if (!File.Exists(projectPath))
{
throw new InvalidOperationException($"{projectPath} does not exist.");
}
return projectPath;
}
private class Summary
{
public int Passed { get; set; }
public int Failed { get; set; }
private int Total => Passed + Failed;
public void Print()
{
var summaryMessage = $"SUMMARY: Total: {Total} targets, Passed: {Passed}, Failed: {Failed}.";
if (Failed > 0)
{
Reporter.Error.WriteLine(summaryMessage.Red());
}
else
{
Reporter.Output.WriteLine(summaryMessage);
}
}
}
}
}

View file

@ -97,6 +97,20 @@ namespace Microsoft.Dotnet.Tools.Test.Tests
result.Should().Pass();
}
[Fact]
public void It_runs_tests_for_an_assembly_passed_as_param()
{
var buildCommand = new BuildCommand(_projectFilePath);
var result = buildCommand.Execute();
result.Should().Pass();
var assemblyUnderTestPath = Path.Combine(_defaultOutputPath, buildCommand.GetPortableOutputName());
var testCommand = new DotnetTestCommand();
result = testCommand.Execute($"{assemblyUnderTestPath}");
result.Should().Pass();
}
[Theory]
[MemberData("ArgumentNames")]
public void It_fails_correctly_with_unspecified_arguments_with_long_form(string argument)