Merge pull request #4102 from livarcocc/test_without_pj

Adding the capability to run tests against a published dll
This commit is contained in:
Livar 2016-08-29 21:58:13 -07:00 committed by GitHub
commit be8428cb6c
93 changed files with 1421 additions and 472 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

@ -26,8 +26,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{17735A9D-B
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tools", "tools", "{0722D325-24C8-4E83-B5AF-0A083E7F0749}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "MultiProjectValidator", "tools\MultiProjectValidator\MultiProjectValidator.xproj", "{08A68C6A-86F6-4ED2-89A7-B166D33E9F85}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "EndToEnd", "test\EndToEnd\EndToEnd.xproj", "{65741CB1-8AEE-4C66-8198-10A7EA0E4258}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "TestProjects", "TestProjects", "{713CBFBB-5392-438D-B766-A9A585EF1BB8}"
@ -104,8 +102,6 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "update-dependencies", "buil
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.DotNet.ProjectModel.Loader", "src\Microsoft.DotNet.ProjectModel.Loader\Microsoft.DotNet.ProjectModel.Loader.xproj", "{1C599FFD-FB52-4279-A8E5-465D3EC499E1}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.DotNet.ProjectModel.Loader.Tests", "test\Microsoft.DotNet.ProjectModel.Loader.Tests\Microsoft.DotNet.ProjectModel.Loader.Tests.xproj", "{5DF6C9DA-6909-4EC0-909E-6913580BB4A4}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.DotNet.Configurer", "src\Microsoft.DotNet.Configurer\Microsoft.DotNet.Configurer.xproj", "{E5ED47EF-BF25-4DA9-A7FE-290C642CBF0F}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.DotNet.Configurer.UnitTests", "test\Microsoft.DotNet.Configurer.UnitTests\Microsoft.DotNet.Configurer.UnitTests.xproj", "{4C3B06D5-B6D5-4E5B-A44F-3EBE52A1C759}"
@ -156,6 +152,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "publish", "publish", "{27B1
build\publish\PublishContent.targets = build\publish\PublishContent.targets
EndProjectSection
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.DotNet.Tools.Test", "src\Microsoft.DotNet.Tools.Test\Microsoft.DotNet.Tools.Test.xproj", "{6D028154-5518-4A56-BAD6-938A90E5BCF6}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -248,22 +246,6 @@ Global
{DCDFE282-03DE-4DBC-B90C-CC3CE3EC8162}.RelWithDebInfo|Any CPU.Build.0 = Release|Any CPU
{DCDFE282-03DE-4DBC-B90C-CC3CE3EC8162}.RelWithDebInfo|x64.ActiveCfg = Release|Any CPU
{DCDFE282-03DE-4DBC-B90C-CC3CE3EC8162}.RelWithDebInfo|x64.Build.0 = Release|Any CPU
{688870C8-9843-4F9E-8576-D39290AD0F25}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{688870C8-9843-4F9E-8576-D39290AD0F25}.Debug|Any CPU.Build.0 = Debug|Any CPU
{688870C8-9843-4F9E-8576-D39290AD0F25}.Debug|x64.ActiveCfg = Debug|Any CPU
{688870C8-9843-4F9E-8576-D39290AD0F25}.Debug|x64.Build.0 = Debug|Any CPU
{688870C8-9843-4F9E-8576-D39290AD0F25}.MinSizeRel|Any CPU.ActiveCfg = Debug|Any CPU
{688870C8-9843-4F9E-8576-D39290AD0F25}.MinSizeRel|Any CPU.Build.0 = Debug|Any CPU
{688870C8-9843-4F9E-8576-D39290AD0F25}.MinSizeRel|x64.ActiveCfg = Debug|Any CPU
{688870C8-9843-4F9E-8576-D39290AD0F25}.MinSizeRel|x64.Build.0 = Debug|Any CPU
{688870C8-9843-4F9E-8576-D39290AD0F25}.Release|Any CPU.ActiveCfg = Release|Any CPU
{688870C8-9843-4F9E-8576-D39290AD0F25}.Release|Any CPU.Build.0 = Release|Any CPU
{688870C8-9843-4F9E-8576-D39290AD0F25}.Release|x64.ActiveCfg = Release|Any CPU
{688870C8-9843-4F9E-8576-D39290AD0F25}.Release|x64.Build.0 = Release|Any CPU
{688870C8-9843-4F9E-8576-D39290AD0F25}.RelWithDebInfo|Any CPU.ActiveCfg = Release|Any CPU
{688870C8-9843-4F9E-8576-D39290AD0F25}.RelWithDebInfo|Any CPU.Build.0 = Release|Any CPU
{688870C8-9843-4F9E-8576-D39290AD0F25}.RelWithDebInfo|x64.ActiveCfg = Release|Any CPU
{688870C8-9843-4F9E-8576-D39290AD0F25}.RelWithDebInfo|x64.Build.0 = Release|Any CPU
{65741CB1-8AEE-4C66-8198-10A7EA0E4258}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{65741CB1-8AEE-4C66-8198-10A7EA0E4258}.Debug|Any CPU.Build.0 = Debug|Any CPU
{65741CB1-8AEE-4C66-8198-10A7EA0E4258}.Debug|x64.ActiveCfg = Debug|Any CPU
@ -776,22 +758,6 @@ Global
{1C599FFD-FB52-4279-A8E5-465D3EC499E1}.RelWithDebInfo|Any CPU.Build.0 = Release|Any CPU
{1C599FFD-FB52-4279-A8E5-465D3EC499E1}.RelWithDebInfo|x64.ActiveCfg = Release|Any CPU
{1C599FFD-FB52-4279-A8E5-465D3EC499E1}.RelWithDebInfo|x64.Build.0 = Release|Any CPU
{5DF6C9DA-6909-4EC0-909E-6913580BB4A4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5DF6C9DA-6909-4EC0-909E-6913580BB4A4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5DF6C9DA-6909-4EC0-909E-6913580BB4A4}.Debug|x64.ActiveCfg = Debug|Any CPU
{5DF6C9DA-6909-4EC0-909E-6913580BB4A4}.Debug|x64.Build.0 = Debug|Any CPU
{5DF6C9DA-6909-4EC0-909E-6913580BB4A4}.MinSizeRel|Any CPU.ActiveCfg = Debug|Any CPU
{5DF6C9DA-6909-4EC0-909E-6913580BB4A4}.MinSizeRel|Any CPU.Build.0 = Debug|Any CPU
{5DF6C9DA-6909-4EC0-909E-6913580BB4A4}.MinSizeRel|x64.ActiveCfg = Debug|Any CPU
{5DF6C9DA-6909-4EC0-909E-6913580BB4A4}.MinSizeRel|x64.Build.0 = Debug|Any CPU
{5DF6C9DA-6909-4EC0-909E-6913580BB4A4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5DF6C9DA-6909-4EC0-909E-6913580BB4A4}.Release|Any CPU.Build.0 = Release|Any CPU
{5DF6C9DA-6909-4EC0-909E-6913580BB4A4}.Release|x64.ActiveCfg = Release|Any CPU
{5DF6C9DA-6909-4EC0-909E-6913580BB4A4}.Release|x64.Build.0 = Release|Any CPU
{5DF6C9DA-6909-4EC0-909E-6913580BB4A4}.RelWithDebInfo|Any CPU.ActiveCfg = Release|Any CPU
{5DF6C9DA-6909-4EC0-909E-6913580BB4A4}.RelWithDebInfo|Any CPU.Build.0 = Release|Any CPU
{5DF6C9DA-6909-4EC0-909E-6913580BB4A4}.RelWithDebInfo|x64.ActiveCfg = Release|Any CPU
{5DF6C9DA-6909-4EC0-909E-6913580BB4A4}.RelWithDebInfo|x64.Build.0 = Release|Any CPU
{E5ED47EF-BF25-4DA9-A7FE-290C642CBF0F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E5ED47EF-BF25-4DA9-A7FE-290C642CBF0F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E5ED47EF-BF25-4DA9-A7FE-290C642CBF0F}.Debug|x64.ActiveCfg = Debug|Any CPU
@ -872,6 +838,22 @@ Global
{E4F46EAB-B5A5-4E60-9B9D-40A1FADBF45C}.RelWithDebInfo|Any CPU.Build.0 = Release|Any CPU
{E4F46EAB-B5A5-4E60-9B9D-40A1FADBF45C}.RelWithDebInfo|x64.ActiveCfg = Release|Any CPU
{E4F46EAB-B5A5-4E60-9B9D-40A1FADBF45C}.RelWithDebInfo|x64.Build.0 = Release|Any CPU
{6D028154-5518-4A56-BAD6-938A90E5BCF6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6D028154-5518-4A56-BAD6-938A90E5BCF6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6D028154-5518-4A56-BAD6-938A90E5BCF6}.Debug|x64.ActiveCfg = Debug|x64
{6D028154-5518-4A56-BAD6-938A90E5BCF6}.Debug|x64.Build.0 = Debug|x64
{6D028154-5518-4A56-BAD6-938A90E5BCF6}.MinSizeRel|Any CPU.ActiveCfg = MinSizeRel|Any CPU
{6D028154-5518-4A56-BAD6-938A90E5BCF6}.MinSizeRel|Any CPU.Build.0 = MinSizeRel|Any CPU
{6D028154-5518-4A56-BAD6-938A90E5BCF6}.MinSizeRel|x64.ActiveCfg = MinSizeRel|x64
{6D028154-5518-4A56-BAD6-938A90E5BCF6}.MinSizeRel|x64.Build.0 = MinSizeRel|x64
{6D028154-5518-4A56-BAD6-938A90E5BCF6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6D028154-5518-4A56-BAD6-938A90E5BCF6}.Release|Any CPU.Build.0 = Release|Any CPU
{6D028154-5518-4A56-BAD6-938A90E5BCF6}.Release|x64.ActiveCfg = Release|x64
{6D028154-5518-4A56-BAD6-938A90E5BCF6}.Release|x64.Build.0 = Release|x64
{6D028154-5518-4A56-BAD6-938A90E5BCF6}.RelWithDebInfo|Any CPU.ActiveCfg = RelWithDebInfo|Any CPU
{6D028154-5518-4A56-BAD6-938A90E5BCF6}.RelWithDebInfo|Any CPU.Build.0 = RelWithDebInfo|Any CPU
{6D028154-5518-4A56-BAD6-938A90E5BCF6}.RelWithDebInfo|x64.ActiveCfg = RelWithDebInfo|x64
{6D028154-5518-4A56-BAD6-938A90E5BCF6}.RelWithDebInfo|x64.Build.0 = RelWithDebInfo|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -882,7 +864,6 @@ Global
{A16958E1-24C7-4F1E-B317-204AD91625DD} = {ED2FE3E2-F7E7-4389-8231-B65123F2076F}
{BD7833F8-3209-4682-BF75-B4BCA883E279} = {ED2FE3E2-F7E7-4389-8231-B65123F2076F}
{DCDFE282-03DE-4DBC-B90C-CC3CE3EC8162} = {ED2FE3E2-F7E7-4389-8231-B65123F2076F}
{688870C8-9843-4F9E-8576-D39290AD0F25} = {ED2FE3E2-F7E7-4389-8231-B65123F2076F}
{65741CB1-8AEE-4C66-8198-10A7EA0E4258} = {17735A9D-BFD9-4585-A7CB-3208CA6EA8A7}
{713CBFBB-5392-438D-B766-A9A585EF1BB8} = {17735A9D-BFD9-4585-A7CB-3208CA6EA8A7}
{60CF7E6C-D6C8-439D-B7B7-D8A27E29BE2C} = {ED2FE3E2-F7E7-4389-8231-B65123F2076F}
@ -920,7 +901,6 @@ Global
{B768BD29-12BF-4C7C-B093-03193FE244D1} = {88278B81-7649-45DC-8A6A-D3A645C5AFC3}
{A28BD8AC-DF15-4F58-8299-98A9AE2B8726} = {88278B81-7649-45DC-8A6A-D3A645C5AFC3}
{1C599FFD-FB52-4279-A8E5-465D3EC499E1} = {ED2FE3E2-F7E7-4389-8231-B65123F2076F}
{5DF6C9DA-6909-4EC0-909E-6913580BB4A4} = {17735A9D-BFD9-4585-A7CB-3208CA6EA8A7}
{E5ED47EF-BF25-4DA9-A7FE-290C642CBF0F} = {ED2FE3E2-F7E7-4389-8231-B65123F2076F}
{4C3B06D5-B6D5-4E5B-A44F-3EBE52A1C759} = {17735A9D-BFD9-4585-A7CB-3208CA6EA8A7}
{35B19F22-B8C0-4849-9C35-3F809B7588B8} = {ED2FE3E2-F7E7-4389-8231-B65123F2076F}
@ -930,5 +910,6 @@ Global
{FF498306-2DE2-47F6-8C35-3CF0589CF2B8} = {89905EC4-BC0F-443B-8ADF-691321F10108}
{E4F46EAB-B5A5-4E60-9B9D-40A1FADBF45C} = {17735A9D-BFD9-4585-A7CB-3208CA6EA8A7}
{27B12960-ABB0-4903-9C60-5E9157E659C8} = {89905EC4-BC0F-443B-8ADF-691321F10108}
{6D028154-5518-4A56-BAD6-938A90E5BCF6} = {ED2FE3E2-F7E7-4389-8231-B65123F2076F}
EndGlobalSection
EndGlobal

View file

@ -43,6 +43,10 @@
<ProjectName>Microsoft.Extensions.Testing.Abstractions</ProjectName>
<Version>$(SdkNugetVersion)</Version>
</ProjectsToPack>
<ProjectsToPack Include="$(ProjectsSrcDirectory)/Microsoft.DotNet.Tools.Test">
<ProjectName>Microsoft.DotNet.Tools.Test</ProjectName>
<Version>$(SdkNugetVersion)</Version>
</ProjectsToPack>
</ItemGroup>
</Target>

View file

@ -56,22 +56,20 @@ namespace Microsoft.DotNet.Cli.Utils
/// array will be present in the corresponding argument array
/// in the command's process.
/// </summary>
/// <param name="commandName"></param>
/// <param name="args"></param>
/// <param name="framework"></param>
/// <returns></returns>
public static Command Create(
string commandName,
IEnumerable<string> args,
NuGetFramework framework = null,
string configuration = Constants.DefaultConfiguration,
string outputPath = null)
string outputPath = null,
string applicationName = null)
{
var commandSpec = CommandResolver.TryResolveCommandSpec(commandName,
args,
framework,
configuration: configuration,
outputPath: outputPath);
outputPath: outputPath,
applicationName: applicationName);
if (commandSpec == null)
{

View file

@ -1,8 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.DotNet.ProjectModel;
using Microsoft.DotNet.ProjectModel.Graph;
using System.Collections.Generic;
using NuGet.Frameworks;
namespace Microsoft.DotNet.Cli.Utils
@ -26,5 +22,7 @@ namespace Microsoft.DotNet.Cli.Utils
public string BuildBasePath { get; set; }
public string DepsJsonFile { get; set; }
public string ApplicationName { get; set; }
}
}

View file

@ -8,6 +8,7 @@ namespace Microsoft.DotNet.Cli.Utils
{
var environment = new EnvironmentProvider();
var packagedCommandSpecFactory = new PackagedCommandSpecFactory();
var publishedPathCommandSpecFactory = new PublishPathCommandSpecFactory();
var platformCommandSpecFactory = default(IPlatformCommandSpecFactory);
if (RuntimeEnvironment.OperatingSystemPlatform == Platform.Windows)
@ -19,13 +20,18 @@ namespace Microsoft.DotNet.Cli.Utils
platformCommandSpecFactory = new GenericPlatformCommandSpecFactory();
}
return CreateDefaultCommandResolver(environment, packagedCommandSpecFactory, platformCommandSpecFactory);
return CreateDefaultCommandResolver(
environment,
packagedCommandSpecFactory,
platformCommandSpecFactory,
publishedPathCommandSpecFactory);
}
public static CompositeCommandResolver CreateDefaultCommandResolver(
IEnvironmentProvider environment,
IPackagedCommandSpecFactory packagedCommandSpecFactory,
IPlatformCommandSpecFactory platformCommandSpecFactory)
IPlatformCommandSpecFactory platformCommandSpecFactory,
IPublishedPathCommandSpecFactory publishedPathCommandSpecFactory)
{
var compositeCommandResolver = new CompositeCommandResolver();
@ -33,8 +39,12 @@ namespace Microsoft.DotNet.Cli.Utils
compositeCommandResolver.AddCommandResolver(new RootedCommandResolver());
compositeCommandResolver.AddCommandResolver(new ProjectToolsCommandResolver(packagedCommandSpecFactory));
compositeCommandResolver.AddCommandResolver(new AppBaseDllCommandResolver());
compositeCommandResolver.AddCommandResolver(new AppBaseCommandResolver(environment, platformCommandSpecFactory));
compositeCommandResolver.AddCommandResolver(new PathCommandResolver(environment, platformCommandSpecFactory));
compositeCommandResolver.AddCommandResolver(
new AppBaseCommandResolver(environment, platformCommandSpecFactory));
compositeCommandResolver.AddCommandResolver(
new PathCommandResolver(environment, platformCommandSpecFactory));
compositeCommandResolver.AddCommandResolver(
new PublishedPathCommandResolver(environment, publishedPathCommandSpecFactory));
return compositeCommandResolver;
}

View file

@ -1,10 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.DotNet.ProjectModel;
using Microsoft.DotNet.ProjectModel.Graph;
using Microsoft.DotNet.ProjectModel.Compilation;
namespace Microsoft.DotNet.Cli.Utils
{

View file

@ -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.Collections.Generic;
namespace Microsoft.DotNet.Cli.Utils
{
public interface IPublishedPathCommandSpecFactory
{
CommandSpec CreateCommandSpecFromPublishFolder(
string commandPath,
IEnumerable<string> commandArguments,
CommandResolutionStrategy commandResolutionStrategy,
string depsFilePath,
string runtimeConfigPath);
}
}

View file

@ -51,7 +51,8 @@ namespace Microsoft.DotNet.Cli.Utils
return null;
}
var buildOutputPath = projectContext.GetOutputPaths(configuration, buildBasePath, outputPath).RuntimeFiles.BasePath;
var buildOutputPath =
projectContext.GetOutputPaths(configuration, buildBasePath, outputPath).RuntimeFiles.BasePath;
if (! Directory.Exists(buildOutputPath))
{

View file

@ -0,0 +1,96 @@
// 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.IO;
using Microsoft.DotNet.ProjectModel;
namespace Microsoft.DotNet.Cli.Utils
{
public class PublishPathCommandSpecFactory : IPublishedPathCommandSpecFactory
{
public CommandSpec CreateCommandSpecFromPublishFolder(
string commandPath,
IEnumerable<string> commandArguments,
CommandResolutionStrategy commandResolutionStrategy,
string depsFilePath,
string runtimeConfigPath)
{
return CreateCommandSpecWrappingWithMuxerIfDll(
commandPath,
commandArguments,
depsFilePath,
commandResolutionStrategy,
runtimeConfigPath);
}
private CommandSpec CreateCommandSpecWrappingWithMuxerIfDll(
string commandPath,
IEnumerable<string> commandArguments,
string depsFilePath,
CommandResolutionStrategy commandResolutionStrategy,
string runtimeConfigPath)
{
var commandExtension = Path.GetExtension(commandPath);
if (commandExtension == FileNameSuffixes.DotNet.DynamicLib)
{
return CreatePackageCommandSpecUsingMuxer(
commandPath,
commandArguments,
depsFilePath,
commandResolutionStrategy,
runtimeConfigPath);
}
return CreateCommandSpec(commandPath, commandArguments, commandResolutionStrategy);
}
private CommandSpec CreatePackageCommandSpecUsingMuxer(
string commandPath,
IEnumerable<string> commandArguments,
string depsFilePath,
CommandResolutionStrategy commandResolutionStrategy,
string runtimeConfigPath)
{
var arguments = new List<string>();
var muxer = new Muxer();
var host = muxer.MuxerPath;
if (host == null)
{
throw new Exception("Unable to locate dotnet multiplexer");
}
arguments.Add("exec");
if (runtimeConfigPath != null)
{
arguments.Add("--runtimeconfig");
arguments.Add(runtimeConfigPath);
}
if (depsFilePath != null)
{
arguments.Add("--depsfile");
arguments.Add(depsFilePath);
}
arguments.Add(commandPath);
arguments.AddRange(commandArguments);
return CreateCommandSpec(host, arguments, commandResolutionStrategy);
}
private CommandSpec CreateCommandSpec(
string commandPath,
IEnumerable<string> commandArguments,
CommandResolutionStrategy commandResolutionStrategy)
{
var escapedArgs = ArgumentEscaper.EscapeAndConcatenateArgArrayForProcessStart(commandArguments);
return new CommandSpec(commandPath, escapedArgs, commandResolutionStrategy);
}
}
}

View file

@ -0,0 +1,72 @@
// 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;
namespace Microsoft.DotNet.Cli.Utils
{
public class PublishedPathCommandResolver : ICommandResolver
{
private readonly IEnvironmentProvider _environment;
private readonly IPublishedPathCommandSpecFactory _commandSpecFactory;
public PublishedPathCommandResolver(
IEnvironmentProvider environment,
IPublishedPathCommandSpecFactory commandSpecFactory)
{
_environment = environment;
_commandSpecFactory = commandSpecFactory;
}
public CommandSpec Resolve(CommandResolverArguments commandResolverArguments)
{
var publishDirectory = commandResolverArguments.OutputPath;
var commandName = commandResolverArguments.CommandName;
var applicationName = commandResolverArguments.ApplicationName;
if (publishDirectory == null || commandName == null || applicationName == null)
{
return null;
}
var commandPath = ResolveCommandPath(publishDirectory, commandName);
if (commandPath == null)
{
return null;
}
var depsFilePath = Path.Combine(publishDirectory, $"{applicationName}.deps.json");
if (!File.Exists(depsFilePath))
{
Reporter.Verbose.WriteLine($"PublishedPathCommandResolver: {depsFilePath} does not exist");
return null;
}
var runtimeConfigPath = Path.Combine(publishDirectory, $"{applicationName}.runtimeconfig.json");
if (!File.Exists(runtimeConfigPath))
{
Reporter.Verbose.WriteLine($"projectdependenciescommandresolver: {runtimeConfigPath} does not exist");
return null;
}
return _commandSpecFactory.CreateCommandSpecFromPublishFolder(
commandPath,
commandResolverArguments.CommandArguments.OrEmptyIfNull(),
CommandResolutionStrategy.OutputPath,
depsFilePath,
runtimeConfigPath);
}
private string ResolveCommandPath(string publishDirectory, string commandName)
{
if (!Directory.Exists(publishDirectory))
{
Reporter.Verbose.WriteLine($"publishedpathresolver: {publishDirectory} does not exist");
return null;
}
return _environment.GetCommandPathFromRootPath(publishDirectory, commandName, ".dll");
}
}
}

View file

@ -12,7 +12,8 @@ namespace Microsoft.DotNet.Cli.Utils
IEnumerable<string> args,
NuGetFramework framework = null,
string configuration = Constants.DefaultConfiguration,
string outputPath = null)
string outputPath = null,
string applicationName = null)
{
var commandResolverArgs = new CommandResolverArguments
{
@ -21,7 +22,8 @@ namespace Microsoft.DotNet.Cli.Utils
Framework = framework,
ProjectDirectory = Directory.GetCurrentDirectory(),
Configuration = configuration,
OutputPath = outputPath
OutputPath = outputPath,
ApplicationName = applicationName
};
var defaultCommandResolver = DefaultCommandResolverPolicy.Create();

View file

@ -0,0 +1,29 @@
// 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 NuGet.Frameworks;
namespace Microsoft.DotNet.Cli.Utils
{
public class PublishedPathCommandFactory : ICommandFactory
{
private readonly string _publishDirectory;
private readonly string _applicationName;
public PublishedPathCommandFactory(string publishDirectory, string applicationName)
{
_publishDirectory = publishDirectory;
_applicationName = applicationName;
}
public ICommand Create(
string commandName,
IEnumerable<string> args,
NuGetFramework framework = null,
string configuration = Constants.DefaultConfiguration)
{
return Command.Create(commandName, args, framework, configuration, _publishDirectory, _applicationName);
}
}
}

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.Collections.Generic;
using System.IO;
using Microsoft.DotNet.InternalAbstractions;
@ -17,5 +18,29 @@ namespace Microsoft.Extensions.EnvironmentAbstractions
{
return new TemporaryDirectory();
}
public IEnumerable<string> GetFiles(string path, string searchPattern)
{
return Directory.GetFiles(path, searchPattern);
}
public string GetDirectoryFullName(string path)
{
var directoryFullName = string.Empty;
if (Exists(path))
{
directoryFullName = new DirectoryInfo(path).FullName;
}
else
{
var fileInfo = new FileInfo(path);
if (fileInfo.Directory != null)
{
directoryFullName = fileInfo.Directory.FullName;
}
}
return directoryFullName;
}
}
}

View file

@ -1,6 +1,8 @@
// 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.Extensions.EnvironmentAbstractions
{
internal interface IDirectory
@ -8,5 +10,9 @@ namespace Microsoft.Extensions.EnvironmentAbstractions
bool Exists(string path);
ITemporaryDirectory CreateTemporaryDirectory();
IEnumerable<string> GetFiles(string path, string searchPattern);
string GetDirectoryFullName(string path);
}
}

View file

@ -7,4 +7,6 @@ using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Microsoft.Extensions.DependencyModel.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
[assembly: InternalsVisibleTo("Microsoft.DotNet.Configurer, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
[assembly: InternalsVisibleTo("Microsoft.DotNet.Configurer.UnitTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")]
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")]
[assembly: InternalsVisibleTo("dotnet-test.UnitTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100039ac461fa5c82c7dd2557400c4fd4e9dcdf7ac47e3d572548c04cd4673e004916610f4ea5cbf86f2b1ca1cb824f2a7b3976afecfcf4eb72d9a899aa6786effa10c30399e6580ed848231fec48374e41b3acf8811931343fc2f73acf72dae745adbcb7063cc4b50550618383202875223fc75401351cd89c44bf9b50e7fa3796")]
[assembly: InternalsVisibleTo("Microsoft.DotNet.Tools.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]

View file

@ -0,0 +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.
namespace Microsoft.DotNet.ProjectModel
{
public interface IProjectReader
{
Project ReadProject(string projectPath, ProjectReaderSettings settings = null);
}
}

View file

@ -15,7 +15,7 @@ using NuGet.Versioning;
namespace Microsoft.DotNet.ProjectModel
{
public class ProjectReader
public class ProjectReader : IProjectReader
{
public static bool TryGetProject(string path, out Project project, ProjectReaderSettings settings = null)
{
@ -63,6 +63,11 @@ namespace Microsoft.DotNet.ProjectModel
}
public static Project GetProject(string projectPath, ProjectReaderSettings settings = null)
{
return new ProjectReader().ReadProject(projectPath, settings);
}
public Project ReadProject(string projectPath, ProjectReaderSettings settings)
{
projectPath = ProjectPathHelper.NormalizeProjectFilePath(projectPath);
@ -70,7 +75,7 @@ namespace Microsoft.DotNet.ProjectModel
using (var stream = new FileStream(projectPath, FileMode.Open, FileAccess.Read, FileShare.Read))
{
return new ProjectReader().ReadProject(stream, name, projectPath, settings);
return ReadProject(stream, name, projectPath, settings);
}
}

View file

@ -27,3 +27,7 @@ using System.Runtime.InteropServices;
[assembly: AssemblyMetadataAttribute("Serviceable", "True")]
[assembly: InternalsVisibleTo("Microsoft.DotNet.ProjectModel.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100039ac461fa5c82c7dd2557400c4fd4e9dcdf7ac47e3d572548c04cd4673e004916610f4ea5cbf86f2b1ca1cb824f2a7b3976afecfcf4eb72d9a899aa6786effa10c30399e6580ed848231fec48374e41b3acf8811931343fc2f73acf72dae745adbcb7063cc4b50550618383202875223fc75401351cd89c44bf9b50e7fa3796")]
[assembly:
InternalsVisibleTo(
"dotnet-test.UnitTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100039ac461fa5c82c7dd2557400c4fd4e9dcdf7ac47e3d572548c04cd4673e004916610f4ea5cbf86f2b1ca1cb824f2a7b3976afecfcf4eb72d9a899aa6786effa10c30399e6580ed848231fec48374e41b3acf8811931343fc2f73acf72dae745adbcb7063cc4b50550618383202875223fc75401351cd89c44bf9b50e7fa3796"
)]

View file

@ -0,0 +1,32 @@
// 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;
namespace Microsoft.DotNet.Tools.Test
{
public class AssemblyTestRunnerDecorator : IDotnetTestRunner
{
private readonly Func<ICommandFactory, string, IDotnetTestRunner> _nextRunner;
public AssemblyTestRunnerDecorator(Func<ICommandFactory, string, IDotnetTestRunner> nextRunner)
{
_nextRunner = nextRunner;
}
public int RunTests(DotnetTestParams dotnetTestParams)
{
var assembly = new FileInfo(dotnetTestParams.ProjectOrAssemblyPath);
var publishDirectory = assembly.Directory.FullName;
var applicationName = Path.GetFileNameWithoutExtension(dotnetTestParams.ProjectOrAssemblyPath);
var commandFactory = new PublishedPathCommandFactory(publishDirectory, applicationName);
var assemblyUnderTest = dotnetTestParams.ProjectOrAssemblyPath;
return _nextRunner(commandFactory, assemblyUnderTest).RunTests(dotnetTestParams);
}
}
}

View file

@ -0,0 +1,55 @@
// 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.DotNet.Cli.Utils;
using NuGet.Frameworks;
namespace Microsoft.DotNet.Tools.Test
{
public class ConsoleTestRunner : IDotnetTestRunner
{
private readonly ITestRunnerNameResolver _testRunnerNameResolver;
private readonly ICommandFactory _commandFactory;
private readonly string _assemblyUnderTest;
private readonly NuGetFramework _framework;
public ConsoleTestRunner(
ITestRunnerNameResolver testRunnerNameResolver,
ICommandFactory commandFactory,
string assemblyUnderTest,
NuGetFramework framework = null)
{
_testRunnerNameResolver = testRunnerNameResolver;
_commandFactory = commandFactory;
_assemblyUnderTest = assemblyUnderTest;
_framework = framework;
}
public int RunTests(DotnetTestParams dotnetTestParams)
{
return _commandFactory.Create(
_testRunnerNameResolver.ResolveTestRunner(),
GetCommandArgs(dotnetTestParams),
_framework,
dotnetTestParams.Config)
.Execute()
.ExitCode;
}
private IEnumerable<string> GetCommandArgs(DotnetTestParams dotnetTestParams)
{
var commandArgs = new List<string>
{
_assemblyUnderTest
};
commandArgs.AddRange(dotnetTestParams.RemainingArguments);
return commandArgs;
}
}
}

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 ITestRunnerNameResolver _testRunnerNameResolver;
private readonly ICommandFactory _commandFactory;
private readonly string _assemblyUnderTest;
public DesignTimeRunner(
ITestRunnerNameResolver testRunnerNameResolver,
ICommandFactory commandFactory,
string assemblyUnderTest)
{
_testRunnerNameResolver = testRunnerNameResolver;
_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(_testRunnerNameResolver.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 $"test-{testRunner}";
}
}
}

View file

@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.IO;
using Microsoft.DotNet.Cli.CommandLine;
using Microsoft.DotNet.Cli.Utils;
using Microsoft.DotNet.ProjectModel;
using Microsoft.DotNet.Tools.Common;
using NuGet.Frameworks;
using static System.Int32;
@ -23,8 +24,9 @@ namespace Microsoft.DotNet.Tools.Test
private CommandOption _configurationOption;
private CommandOption _portOption;
private CommandOption _parentProcessIdOption;
private CommandArgument _projectPath;
private CommandArgument _projectOrAssemblyPath;
private CommandOption _noBuildOption;
private CommandOption _testRunner;
public int? Port { get; set; }
@ -38,7 +40,7 @@ namespace Microsoft.DotNet.Tools.Test
public string Output { get; set; }
public string ProjectPath { get; set; }
public string ProjectOrAssemblyPath { get; set; }
public NuGetFramework Framework { get; set; }
@ -50,6 +52,12 @@ namespace Microsoft.DotNet.Tools.Test
public bool Help { get; set; }
public string TestRunner { get; set; }
public bool HasTestRunner => !string.IsNullOrWhiteSpace(TestRunner);
public bool IsTestingAssembly => ProjectOrAssemblyPath.EndsWith(".dll");
public DotnetTestParams()
{
_app = new CommandLineApplication(false)
@ -69,10 +77,10 @@ namespace Microsoft.DotNet.Tools.Test
_app.OnExecute(() =>
{
// Locate the project and get the name and full path
ProjectPath = _projectPath.Value;
if (string.IsNullOrEmpty(ProjectPath))
ProjectOrAssemblyPath = _projectOrAssemblyPath.Value;
if (string.IsNullOrEmpty(ProjectOrAssemblyPath))
{
ProjectPath = Directory.GetCurrentDirectory();
ProjectOrAssemblyPath = Directory.GetCurrentDirectory();
}
if (_parentProcessIdOption.HasValue())
@ -100,6 +108,16 @@ namespace Microsoft.DotNet.Tools.Test
Port = port;
}
if (_testRunner.HasValue())
{
if (!IsTestingAssembly)
{
throw new InvalidOperationException("You can only specify a test runner with a dll.");
}
TestRunner = _testRunner.Value();
}
UnparsedFramework = _frameworkOption.Value();
if (_frameworkOption.HasValue())
{
@ -156,9 +174,16 @@ namespace Microsoft.DotNet.Tools.Test
CommandOptionType.SingleValue);
_noBuildOption =
_app.Option("--no-build", "Do not build project before testing", CommandOptionType.NoValue);
_projectPath = _app.Argument(
"<PROJECT>",
"The project to test, defaults to the current directory. Can be a path to a project.json or a project directory.");
_testRunner =
_app.Option(
"-t|--test-runner <TEST_RUNNER>",
"Test runner to be used to run the test when an assembly to test is specified. If this option " +
"is not provided, we will try to find a suitable runner collocated with the assembly",
CommandOptionType.SingleValue);
_projectOrAssemblyPath = _app.Argument(
"<PROJECT OR ASSEMBLY TO TEST>",
"The project or assembly to test, defaults to the current directory. Can be a path to a " +
"project.json, to a dll or a project directory.");
}
}
}

View file

@ -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 System;
using Microsoft.DotNet.Cli.Utils;
using NuGet.Frameworks;
namespace Microsoft.DotNet.Tools.Test
{
public class DotnetTestRunnerFactory : IDotnetTestRunnerFactory
{
private readonly DotnetTestRunnerResolverFactory _dotnetTestRunnerResolverFactory;
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 AssemblyTestRunnerDecorator(nextAssemblyTestRunner);
}
private static IDotnetTestRunner CreateTestRunnerForProjectJson(
Func<ICommandFactory, string, NuGetFramework, IDotnetTestRunner> nextTestRunner)
{
return new ProjectJsonTestRunnerDecorator(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

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals">
<ProjectGuid>6D028154-5518-4A56-BAD6-938A90E5BCF6</ProjectGuid>
<RootNamespace>dotnet_test_console</RootNamespace>
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">obj\$(MSBuildProjectName)</BaseIntermediateOutputPath>
<OutputPath Condition="'$(OutputPath)'=='' ">bin\$(MSBuildProjectName)\</OutputPath>
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>

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 ProjectJsonTestRunnerDecorator : IDotnetTestRunner
{
private readonly Func<ICommandFactory, string, NuGetFramework, IDotnetTestRunner> _nextRunner;
private readonly TestProjectBuilder _testProjectBuilder;
public ProjectJsonTestRunnerDecorator(
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

@ -0,0 +1,5 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
[assembly: InternalsVisibleTo("dotnet-test.UnitTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100039ac461fa5c82c7dd2557400c4fd4e9dcdf7ac47e3d572548c04cd4673e004916610f4ea5cbf86f2b1ca1cb824f2a7b3976afecfcf4eb72d9a899aa6786effa10c30399e6580ed848231fec48374e41b3acf8811931343fc2f73acf72dae745adbcb7063cc4b50550618383202875223fc75401351cd89c44bf9b50e7fa3796")]

View file

@ -8,20 +8,20 @@
## SYNOPSIS
`dotnet test [--configuration]
`dotnet test [--configuration]
[--output] [--build-base-path] [--framework] [--runtime]
[--no-build]
[--parentProcessId] [--port]
[<project>]`
[--parentProcessId] [--port]
[<project>]`
## DESCRIPTION
The `dotnet test` command is used to execute unit tests in a given project. Unit tests are class library
projects that have dependencies on the unit test framework (for example, NUnit or xUnit) and the
dotnet test runner for that unit testing framework.
The `dotnet test` command is used to execute unit tests in a given project. Unit tests are class library
projects that have dependencies on the unit test framework (for example, NUnit or xUnit) and the
dotnet test runner for that unit testing framework.
These are packaged as NuGet packages and are restored as ordinary dependencies for the project.
Test projects also need to specify a test runner property in project.json using the "testRunner" node.
Test projects also need to specify a test runner property in project.json using the "testRunner" node.
This value should contain the name of the unit test framework.
The following sample project.json shows the properties needed:
@ -53,17 +53,17 @@ The following sample project.json shows the properties needed:
`dotnet test` supports two running modes:
1. Console: In console mode, `dotnet test` simply executes fully any command gets passed to it and outputs the results. Anytime you invoke `dotnet test` without passing --port, it runs in console mode, which in turn will cause the runner to run in console mode.
2. Design time: used in the context of other tools, such as editors or Integrated Development Environments (IDEs). You can find out more about this mode in the [dotnet-test protocol](../../../../Documentation/dotnet-test-protocol.md) document.
2. Design time: used in the context of other tools, such as editors or Integrated Development Environments (IDEs). You can find out more about this mode in the [dotnet-test protocol](../../../../Documentation/dotnet-test-protocol.md) document.
## OPTIONS
`[project]`
Specifies a path to the test project. If omitted, it defaults to current directory.
Specifies a path to the test project. If omitted, it defaults to current directory.
`-c`, `--configuration` [Debug|Release]
Configuration under which to build. The default value is Release.
Configuration under which to build. The default value is Release.
`-o`, `--output` [DIR]
@ -81,9 +81,9 @@ Looks for test binaries for a specific framework.
Look for test binaries for a for the specified runtime.
`--no-build`
`--no-build`
Does not build the test project prior to running it.
Does not build the test project prior to running it.
--parentProcessId
@ -97,8 +97,8 @@ Used by IDEs to specify a port number to listen for a connection.
`dotnet test`
Runs the tests in the project in the current directory.
Runs the tests in the project in the current directory.
`dotnet test /projects/test1/project.json`
Runs the tests in the test1 project.
Runs the tests in the test1 project.

View file

@ -0,0 +1,100 @@
// 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 System.IO;
using System.Linq;
using Microsoft.DotNet.Cli.Utils;
using Microsoft.DotNet.InternalAbstractions;
using Microsoft.DotNet.ProjectModel;
namespace Microsoft.DotNet.Tools.Test
{
public class TestCommand
{
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;
}
public int DoRun(string[] args)
{
DebugHelper.HandleDebugSwitch(ref args);
var dotnetTestParams = new DotnetTestParams();
try
{
dotnetTestParams.Parse(args);
if (dotnetTestParams.Help)
{
return 0;
}
// Register for parent process's exit event
if (dotnetTestParams.ParentProcessId.HasValue)
{
RegisterForParentProcessExit(dotnetTestParams.ParentProcessId.Value);
}
return RunTest(dotnetTestParams);
}
catch (InvalidOperationException ex)
{
TestHostTracing.Source.TraceEvent(TraceEventType.Error, 0, ex.ToString());
return -1;
}
catch (Exception ex) when (!(ex is GracefulException))
{
Console.WriteLine(ex.ToString());
TestHostTracing.Source.TraceEvent(TraceEventType.Error, 0, ex.ToString());
return -2;
}
}
private static void RegisterForParentProcessExit(int id)
{
var parentProcess = Process.GetProcesses().FirstOrDefault(p => p.Id == id);
if (parentProcess != null)
{
parentProcess.EnableRaisingEvents = true;
parentProcess.Exited += (sender, eventArgs) =>
{
TestHostTracing.Source.TraceEvent(
TraceEventType.Information,
0,
"Killing the current process as parent process has exited.");
Process.GetCurrentProcess().Kill();
};
}
else
{
TestHostTracing.Source.TraceEvent(
TraceEventType.Information,
0,
"Failed to register for parent process's exit event. " +
$"Parent process with id '{id}' was not found.");
}
}
private int RunTest(DotnetTestParams dotnetTestParams)
{
var dotnetTestRunner = _dotnetTestRunnerFactory.Create(dotnetTestParams);
return dotnetTestRunner.RunTests(dotnetTestParams);
}
}
}

View file

@ -14,8 +14,8 @@ namespace Microsoft.DotNet.Tools.Test
static TestHostTracing()
{
Source = Environment.GetEnvironmentVariable(TracingEnvironmentVariable) == "1"
? new TraceSource("dotnet-test", SourceLevels.Verbose)
Source = Environment.GetEnvironmentVariable(TracingEnvironmentVariable) == "1"
? new TraceSource("dotnet-test", SourceLevels.Verbose)
: new TraceSource("dotnet-test", SourceLevels.Warning);
Source.Listeners.Add(new TextWriterTraceListener(Console.Error));

View file

@ -0,0 +1,52 @@
// 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.DotNet.Cli.Utils;
using Microsoft.DotNet.ProjectModel;
namespace Microsoft.DotNet.Tools.Test
{
public class TestProjectBuilder
{
public int BuildTestProject(ProjectContext projectContext, DotnetTestParams dotnetTestParams)
{
return dotnetTestParams.NoBuild ? 0 : DoBuildTestProject(projectContext, dotnetTestParams);
}
private int DoBuildTestProject(ProjectContext projectContext, DotnetTestParams dotnetTestParams)
{
var strings = new List<string>
{
$"{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.
if (!string.IsNullOrEmpty(dotnetTestParams.BuildBasePath))
{
strings.Add("--build-base-path");
strings.Add(dotnetTestParams.BuildBasePath);
}
if (!string.IsNullOrEmpty(dotnetTestParams.Output))
{
strings.Add("--output");
strings.Add(dotnetTestParams.Output);
}
if (!string.IsNullOrEmpty(projectContext.RuntimeIdentifier))
{
strings.Add("--runtime");
strings.Add(projectContext.RuntimeIdentifier);
}
var result = Command.CreateDotNet("build", strings).Execute().ExitCode;
return result;
}
}
}

View file

@ -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 System.IO;
using System.Linq;
using Microsoft.Extensions.EnvironmentAbstractions;
namespace Microsoft.DotNet.Tools.Test
{
public class AssemblyTestRunnerNameResolver : ITestRunnerNameResolver
{
private readonly string _directoryOfAssemblyUnderTest;
private readonly IDirectory _directory;
public AssemblyTestRunnerNameResolver(string assemblyUnderTest) :
this(assemblyUnderTest, FileSystemWrapper.Default.Directory)
{
}
internal AssemblyTestRunnerNameResolver(string assemblyUnderTest, IDirectory directory)
{
_directoryOfAssemblyUnderTest = directory.GetDirectoryFullName(assemblyUnderTest);
_directory = directory;
}
public string ResolveTestRunner()
{
var testRunnerPath = _directory.GetFiles(_directoryOfAssemblyUnderTest, "dotnet-test-*").FirstOrDefault();
return Path.GetFileNameWithoutExtension(testRunnerPath);
}
}
}

View file

@ -0,0 +1,48 @@
// 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
{
public class DotnetTestRunnerResolverFactory
{
private readonly IProjectReader _projectReader;
public DotnetTestRunnerResolverFactory(IProjectReader projectReader)
{
_projectReader = projectReader;
}
public ITestRunnerNameResolver Create(DotnetTestParams dotnetTestParams)
{
var testRunnerResolver = dotnetTestParams.IsTestingAssembly ?
GetAssemblyTestRunnerResolver(dotnetTestParams) :
GetProjectJsonTestRunnerResolver(dotnetTestParams);
return testRunnerResolver;
}
private ITestRunnerNameResolver GetAssemblyTestRunnerResolver(DotnetTestParams dotnetTestParams)
{
ITestRunnerNameResolver testRunnerNameResolver = null;
if (dotnetTestParams.HasTestRunner)
{
testRunnerNameResolver = new ParameterTestRunnerNameResolver(dotnetTestParams.TestRunner);
}
else
{
testRunnerNameResolver = new AssemblyTestRunnerNameResolver(dotnetTestParams.ProjectOrAssemblyPath);
}
return testRunnerNameResolver;
}
private ITestRunnerNameResolver GetProjectJsonTestRunnerResolver(DotnetTestParams dotnetTestParams)
{
var project = _projectReader.ReadProject(dotnetTestParams.ProjectOrAssemblyPath);
return new ProjectJsonTestRunnerNameResolver(project);
}
}
}

View file

@ -0,0 +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.
namespace Microsoft.DotNet.Tools.Test
{
public interface ITestRunnerNameResolver
{
string ResolveTestRunner();
}
}

View file

@ -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.
namespace Microsoft.DotNet.Tools.Test
{
public class ParameterTestRunnerNameResolver : ITestRunnerNameResolver
{
private readonly string _testRunner;
public ParameterTestRunnerNameResolver(string testRunner)
{
_testRunner = testRunner;
}
public string ResolveTestRunner()
{
return $"dotnet-test-{_testRunner}";
}
}
}

View file

@ -0,0 +1,22 @@
// 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;
namespace Microsoft.DotNet.Tools.Test
{
public class ProjectJsonTestRunnerNameResolver : ITestRunnerNameResolver
{
private Project _project;
public ProjectJsonTestRunnerNameResolver(Project project)
{
_project = project;
}
public string ResolveTestRunner()
{
return string.IsNullOrEmpty(_project.TestRunner) ? null : $"dotnet-test-{_project.TestRunner}";
}
}
}

View file

@ -51,7 +51,7 @@ namespace Microsoft.DotNet.Tools.Test
var commandArgs = _argumentsBuilder.BuildArguments();
return _commandFactory.Create(
$"dotnet-{_testRunner}",
$"{_testRunner}",
commandArgs,
null,
null);

View file

@ -0,0 +1,38 @@
{
"version": "1.0.0-featmsbuild-*",
"buildOptions": {
"keyFile": "../../tools/Key.snk",
"compile": {
"include": [
"**/*.cs",
"../dotnet/CommandLine/*.cs"
]
}
},
"dependencies": {
"System.Diagnostics.TraceSource": "4.0.0",
"System.Diagnostics.TextWriterTraceListener": "4.0.0",
"Microsoft.DotNet.Cli.Utils": {
"target": "project"
},
"Microsoft.DotNet.ProjectModel": {
"target": "project"
},
"Microsoft.Extensions.Testing.Abstractions": {
"target": "project"
},
"Microsoft.DotNet.InternalAbstractions": {
"target": "project"
},
"Microsoft.DotNet.PlatformAbstractions": "1.0.1-beta-000919"
},
"frameworks": {
"netstandard1.6": {
"imports": [
"portable-net45+wp80+win8+wpa81+dnxcore50"
]
}
}
}

View file

@ -5,4 +5,4 @@ using System.Reflection;
using System.Resources;
[assembly: AssemblyMetadata("Serviceable", "True")]
[assembly: NeutralResourcesLanguage("en-us")]
[assembly: NeutralResourcesLanguage("en-us")]

View file

@ -1,67 +0,0 @@
// 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.DotNet.ProjectModel;
namespace Microsoft.DotNet.Tools.Test
{
public abstract class BaseDotnetTestRunner : IDotnetTestRunner
{
public int RunTests(ProjectContext projectContext, DotnetTestParams dotnetTestParams, BuildWorkspace workspace)
{
var result = BuildTestProject(projectContext, dotnetTestParams, workspace);
return result == 0 ? DoRunTests(projectContext, dotnetTestParams) : result;
}
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)
{
var strings = new List<string>
{
$"--configuration",
dotnetTestParams.Config,
$"{dotnetTestParams.ProjectPath}"
};
// 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());
if (!string.IsNullOrEmpty(dotnetTestParams.BuildBasePath))
{
strings.Add("--build-base-path");
strings.Add(dotnetTestParams.BuildBasePath);
}
if (!string.IsNullOrEmpty(dotnetTestParams.Output))
{
strings.Add("--output");
strings.Add(dotnetTestParams.Output);
}
if (!string.IsNullOrEmpty(projectContext.RuntimeIdentifier))
{
strings.Add("--runtime");
strings.Add(projectContext.RuntimeIdentifier);
}
var result = Build.BuildCommand.Run(strings.ToArray(), workspace);
return result;
}
}
}

View file

@ -1,48 +0,0 @@
// 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.DotNet.Cli.Utils;
using Microsoft.DotNet.ProjectModel;
namespace Microsoft.DotNet.Tools.Test
{
public class ConsoleTestRunner : BaseDotnetTestRunner
{
internal override int DoRunTests(ProjectContext projectContext, DotnetTestParams dotnetTestParams)
{
var commandFactory =
new ProjectDependenciesCommandFactory(
projectContext.TargetFramework,
dotnetTestParams.Config,
dotnetTestParams.Output,
dotnetTestParams.BuildBasePath,
projectContext.ProjectDirectory);
return commandFactory.Create(
GetCommandName(projectContext.ProjectFile.TestRunner),
GetCommandArgs(projectContext, dotnetTestParams),
projectContext.TargetFramework,
dotnetTestParams.Config)
.Execute()
.ExitCode;
}
private IEnumerable<string> GetCommandArgs(ProjectContext projectContext, DotnetTestParams dotnetTestParams)
{
var commandArgs = new List<string>
{
new AssemblyUnderTest(projectContext, dotnetTestParams).Path
};
commandArgs.AddRange(dotnetTestParams.RemainingArguments);
return commandArgs;
}
private static string GetCommandName(string testRunner)
{
return $"dotnet-test-{testRunner}";
}
}
}

View file

@ -1,19 +0,0 @@
// 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 DotnetTestRunnerFactory : IDotnetTestRunnerFactory
{
public IDotnetTestRunner Create(int? port)
{
IDotnetTestRunner dotnetTestRunner = new ConsoleTestRunner();
if (port.HasValue)
{
dotnetTestRunner = new DesignTimeRunner();
}
return dotnetTestRunner;
}
}
}

View file

@ -1,191 +0,0 @@
// 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 System.IO;
using System.Linq;
using Microsoft.DotNet.Cli.Utils;
using Microsoft.DotNet.InternalAbstractions;
using Microsoft.DotNet.ProjectModel;
namespace Microsoft.DotNet.Tools.Test
{
public class TestCommand
{
private readonly IDotnetTestRunnerFactory _dotnetTestRunnerFactory;
public TestCommand(IDotnetTestRunnerFactory testRunnerFactory)
{
_dotnetTestRunnerFactory = testRunnerFactory;
}
public int DoRun(string[] args)
{
DebugHelper.HandleDebugSwitch(ref args);
var dotnetTestParams = new DotnetTestParams();
try
{
dotnetTestParams.Parse(args);
if (dotnetTestParams.Help)
{
return 0;
}
// Register for parent process's exit event
if (dotnetTestParams.ParentProcessId.HasValue)
{
RegisterForParentProcessExit(dotnetTestParams.ParentProcessId.Value);
}
var projectPath = GetProjectPath(dotnetTestParams.ProjectPath);
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;
}
catch (InvalidOperationException ex)
{
TestHostTracing.Source.TraceEvent(TraceEventType.Error, 0, ex.ToString());
return -1;
}
catch (Exception ex) when (!(ex is GracefulException))
{
TestHostTracing.Source.TraceEvent(TraceEventType.Error, 0, ex.ToString());
return -2;
}
}
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);
if (parentProcess != null)
{
parentProcess.EnableRaisingEvents = true;
parentProcess.Exited += (sender, eventArgs) =>
{
TestHostTracing.Source.TraceEvent(
TraceEventType.Information,
0,
"Killing the current process as parent process has exited.");
Process.GetCurrentProcess().Kill();
};
}
else
{
TestHostTracing.Source.TraceEvent(
TraceEventType.Information,
0,
"Failed to register for parent process's exit event. " +
$"Parent process with id '{id}' was not found.");
}
}
private int RunTest(ProjectContext projectContext, DotnetTestParams dotnetTestParams, BuildWorkspace workspace)
{
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);
}
}
}
}
}

View file

@ -45,6 +45,9 @@
"Microsoft.DotNet.Configurer": {
"target": "project"
},
"Microsoft.DotNet.Tools.Test": {
"target": "project"
},
"Microsoft.NETCore.App": {
"type": "platform",
"version": "1.0.0"

View file

@ -17,7 +17,7 @@ namespace Microsoft.DotNet.Cli.Utils.Tests
var resolvers = defaultCommandResolver.OrderedCommandResolvers;
resolvers.Should().HaveCount(6);
resolvers.Should().HaveCount(7);
resolvers.Select(r => r.GetType())
.Should()
@ -28,7 +28,8 @@ namespace Microsoft.DotNet.Cli.Utils.Tests
typeof(ProjectToolsCommandResolver),
typeof(AppBaseDllCommandResolver),
typeof(AppBaseCommandResolver),
typeof(PathCommandResolver)
typeof(PathCommandResolver),
typeof(PublishedPathCommandResolver)
});
}
}

View file

@ -118,6 +118,16 @@ namespace Microsoft.Extensions.DependencyModel.Tests
return _temporaryDirectory;
}
public IEnumerable<string> GetFiles(string path, string searchPattern)
{
throw new NotImplementedException();
}
public string GetDirectoryFullName(string path)
{
throw new NotImplementedException();
}
public bool Exists(string path)
{
return _files.Keys.Any(k => k.StartsWith(path));

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 publishCommand = new PublishCommand(_projectFilePath);
var result = publishCommand.Execute();
result.Should().Pass();
var assemblyUnderTestPath = Path.Combine(publishCommand.GetOutputDirectory(true).FullName, publishCommand.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)

View file

@ -15,6 +15,9 @@
"Microsoft.DotNet.ProjectModel": {
"target": "project"
},
"Microsoft.DotNet.InternalAbstractions": {
"target": "project"
},
"System.Net.NameResolution": "4.0.0",
"System.Net.Sockets": "4.1.0",
"System.Runtime.Serialization.Primitives": "4.1.1",

View file

@ -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 FluentAssertions;
using Microsoft.DotNet.Tools.Test;
using Xunit;
namespace Microsoft.Dotnet.Tools.Test.Tests
{
public class GivenAParameterTestRunnerNameResolver
{
private const string SomeTestRunner = "Some test runner";
[Fact]
public void It_returns_the_runner_based_on_the_parameter()
{
var parameterTestRunnerResolver = new ParameterTestRunnerNameResolver(SomeTestRunner);
var testRunner = parameterTestRunnerResolver.ResolveTestRunner();
testRunner.Should().Be($"dotnet-test-{SomeTestRunner}");
}
}
}

View file

@ -0,0 +1,43 @@
// 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.ProjectModel;
using Microsoft.DotNet.Tools.Test;
using Microsoft.Extensions.Testing.Abstractions;
using Xunit;
namespace Microsoft.Dotnet.Tools.Test.Tests
{
public class GivenAProjectJsonTestRunnerNameResolver
{
private const string SomeTestRunner = "runner";
[Fact]
public void It_resolves_the_TestRunner_using_the_testRunner_property_in_the_projectJson()
{
var project = new Project
{
TestRunner = SomeTestRunner
};
var projectJsonTestRunnerResolver = new ProjectJsonTestRunnerNameResolver(project);
var testRunner = projectJsonTestRunnerResolver.ResolveTestRunner();
testRunner.Should().Be($"dotnet-test-{SomeTestRunner}");
}
[Fact]
public void It_returns_null_when_there_is_no_testRunner_set_in_the_projectJson()
{
var project = new Project();
var projectJsonTestRunnerResolver = new ProjectJsonTestRunnerNameResolver(project);
var testRunner = projectJsonTestRunnerResolver.ResolveTestRunner();
testRunner.Should().BeNull();
}
}
}

View file

@ -21,19 +21,21 @@ namespace Microsoft.Dotnet.Tools.Test.Tests
"NetCoreAppOnlyProject",
"project.json");
private TestCommand _testCommand;
private Mock<IDotnetTestRunnerFactory> _dotnetTestRunnerFactoryMock;
private Mock<IDotnetTestRunner> _dotnetTestRunnerMock;
private readonly TestCommand _testCommand;
private readonly Mock<IDotnetTestRunnerFactory> _dotnetTestRunnerFactoryMock;
private readonly Mock<IDotnetTestRunner> _dotnetTestRunnerMock;
public GivenATestCommand()
{
_dotnetTestRunnerMock = new Mock<IDotnetTestRunner>();
_dotnetTestRunnerMock
.Setup(d => d.RunTests(It.IsAny<ProjectContext>(), It.IsAny<DotnetTestParams>(), It.IsAny<BuildWorkspace>()))
.Setup(d => d.RunTests(It.IsAny<DotnetTestParams>()))
.Returns(0);
_dotnetTestRunnerFactoryMock = new Mock<IDotnetTestRunnerFactory>();
_dotnetTestRunnerFactoryMock.Setup(d => d.Create(null)).Returns(_dotnetTestRunnerMock.Object);
_dotnetTestRunnerFactoryMock
.Setup(d => d.Create(It.IsAny<DotnetTestParams>()))
.Returns(_dotnetTestRunnerMock.Object);
_testCommand = new TestCommand(_dotnetTestRunnerFactoryMock.Object);
}
@ -44,7 +46,8 @@ namespace Microsoft.Dotnet.Tools.Test.Tests
var result = _testCommand.DoRun(new[] {"--help"});
result.Should().Be(0);
_dotnetTestRunnerFactoryMock.Verify(d => d.Create(It.IsAny<int?>()), Times.Never);
_dotnetTestRunnerFactoryMock
.Verify(d => d.Create(It.IsAny<DotnetTestParams>()), Times.Never);
}
[Fact]
@ -53,7 +56,8 @@ namespace Microsoft.Dotnet.Tools.Test.Tests
var result = _testCommand.DoRun(new[] { ProjectJsonPath, "-f", "netcoreapp1.0" });
result.Should().Be(0);
_dotnetTestRunnerFactoryMock.Verify(d => d.Create(It.IsAny<int?>()), Times.Once);
_dotnetTestRunnerFactoryMock
.Verify(d => d.Create(It.IsAny<DotnetTestParams>()), Times.Once);
}
[Fact]
@ -62,7 +66,7 @@ namespace Microsoft.Dotnet.Tools.Test.Tests
var result = _testCommand.DoRun(new[] { ProjectJsonPath, "-f", "netcoreapp1.0" });
_dotnetTestRunnerMock.Verify(
d => d.RunTests(It.IsAny<ProjectContext>(), It.IsAny<DotnetTestParams>(), It.IsAny<BuildWorkspace>()),
d => d.RunTests(It.IsAny<DotnetTestParams>()),
Times.Once);
}
}

View file

@ -20,7 +20,7 @@ namespace Microsoft.Dotnet.Tools.Test.Tests
private Mock<ICommand> _commandMock;
private Mock<ICommandFactory> _commandFactoryMock;
private Mock<ITestRunnerArgumentsBuilder> _argumentsBuilderMock;
private string _runner = "runner";
private string _runner = "dotnet-test-runner";
private string[] _testRunnerArguments;
public GivenATestRunner()
@ -38,7 +38,7 @@ namespace Microsoft.Dotnet.Tools.Test.Tests
_commandFactoryMock = new Mock<ICommandFactory>();
_commandFactoryMock.Setup(c => c.Create(
$"dotnet-{_runner}",
$"{_runner}",
_testRunnerArguments,
null,
null)).Returns(_commandMock.Object).Verifiable();

View file

@ -0,0 +1,79 @@
// 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 FluentAssertions;
using Microsoft.DotNet.ProjectModel;
using Microsoft.DotNet.Tools.Test;
using Moq;
using Xunit;
namespace Microsoft.Dotnet.Tools.Test.Tests
{
public class GivenATestRunnerNameResolverFactoryAndADotnetTestParams
{
private const string PathToAFolder = "c:/some/path";
private const string PathToAnAssembly = "c:/some/path/to/assembly.dll";
private const string SomeTestRunner = "some test runner";
private readonly string _pathToAProjectJson = Path.Combine(PathToAFolder, "project.json");
[Fact]
public void It_returns_a_ProjectJsonTestRunnerResolver_when_the_path_parameter_points_to_a_project_json()
{
var dotnetTestParams = new DotnetTestParams
{
ProjectOrAssemblyPath = _pathToAProjectJson
};
var projectReaderMock = new Mock<IProjectReader>();
projectReaderMock
.Setup(p => p.ReadProject(dotnetTestParams.ProjectOrAssemblyPath, null))
.Returns(new Project());
var dotnetTestRunnerResolverFactory = new DotnetTestRunnerResolverFactory(projectReaderMock.Object);
var testRunnerResolver = dotnetTestRunnerResolverFactory.Create(dotnetTestParams);
testRunnerResolver.Should().BeOfType<ProjectJsonTestRunnerNameResolver>();
}
[Fact]
public void It_returns_a_ProjectJsonTestRunnerResolver_when_the_path_parameter_points_to_a_folder()
{
var dotnetTestParams = new DotnetTestParams
{
ProjectOrAssemblyPath = PathToAFolder
};
var projectReaderMock = new Mock<IProjectReader>();
projectReaderMock
.Setup(p => p.ReadProject(dotnetTestParams.ProjectOrAssemblyPath, null))
.Returns(new Project());
var dotnetTestRunnerResolverFactory = new DotnetTestRunnerResolverFactory(projectReaderMock.Object);
var testRunnerResolver = dotnetTestRunnerResolverFactory.Create(dotnetTestParams);
testRunnerResolver.Should().BeOfType<ProjectJsonTestRunnerNameResolver>();
}
[Fact]
public void It_returns_a_ParameterTestRunnerResolver_when_an_assembly_and_a_test_runner_are_passed()
{
var dotnetTestParams = new DotnetTestParams
{
ProjectOrAssemblyPath = PathToAnAssembly,
TestRunner = SomeTestRunner
};
var projectReaderMock = new Mock<IProjectReader>();
var dotnetTestRunnerResolverFactory = new DotnetTestRunnerResolverFactory(projectReaderMock.Object);
var testRunnerResolver = dotnetTestRunnerResolverFactory.Create(dotnetTestParams);
testRunnerResolver.Should().BeOfType<ParameterTestRunnerNameResolver>();
}
}
}

View file

@ -0,0 +1,100 @@
// 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.IO;
using System.Linq;
using System.Text.RegularExpressions;
using FluentAssertions;
using Microsoft.DotNet.Tools.Test;
using Microsoft.Extensions.EnvironmentAbstractions;
using Xunit;
namespace Microsoft.Dotnet.Tools.Test.Tests
{
public class GivenAnAssemblyTestRunnerNameResolver
{
private readonly string _directoryOfAssemblyUnderTest = Path.Combine("c:", "some", "path");
private const string TestRunnerName = "dotnet-test-someRunner";
private static readonly string TestRunnerFileName = $"{TestRunnerName}.dll";
[Fact]
public void It_finds_the_runner_in_the_same_folder_as_the_assembly_when_the_path_passed_is_to_the_assembly()
{
var directoryMock = new DirectoryMock();
directoryMock.AddFile(_directoryOfAssemblyUnderTest, TestRunnerFileName);
var pathToAssemblyUnderTest = Path.Combine(_directoryOfAssemblyUnderTest, TestRunnerFileName);
var assemblyTestRunnerResolver =
new AssemblyTestRunnerNameResolver(pathToAssemblyUnderTest, directoryMock);
var testRunner = assemblyTestRunnerResolver.ResolveTestRunner();
testRunner.Should().Be(TestRunnerName);
}
[Fact]
public void It_returns_a_test_runner_even_when_multiple_test_runners_are_present()
{
var directoryMock = new DirectoryMock();
directoryMock.AddFile(_directoryOfAssemblyUnderTest, TestRunnerFileName);
directoryMock.AddFile(_directoryOfAssemblyUnderTest, "dotnet-test-someOtherTestRunner.dll");
directoryMock.AddFile(_directoryOfAssemblyUnderTest, "dotnet-test-AndYetAnotherTestRunner.dll");
var assemblyTestRunnerResolver =
new AssemblyTestRunnerNameResolver(_directoryOfAssemblyUnderTest, directoryMock);
var bestEffortTestRunner = assemblyTestRunnerResolver.ResolveTestRunner();
bestEffortTestRunner.Should().NotBeNull();
}
[Fact]
public void It_returns_null_when_no_test_runner_is_found()
{
var directoryMock = new DirectoryMock();
var assemblyTestRunnerResolver =
new AssemblyTestRunnerNameResolver(_directoryOfAssemblyUnderTest, directoryMock);
var testRunner = assemblyTestRunnerResolver.ResolveTestRunner();
testRunner.Should().BeNull();
}
private class DirectoryMock : IDirectory
{
private readonly IList<string> _files = new List<string>();
public bool Exists(string path)
{
throw new System.NotImplementedException();
}
public ITemporaryDirectory CreateTemporaryDirectory()
{
throw new System.NotImplementedException();
}
public IEnumerable<string> GetFiles(string path, string searchPattern)
{
var searchPatternRegex = new Regex(searchPattern);
return _files.Where(f => f.StartsWith(path) && searchPatternRegex.IsMatch(f));
}
public string GetDirectoryFullName(string path)
{
return Path.GetDirectoryName(path);
}
public void AddFile(string path, string fileName)
{
_files.Add(Path.Combine(path, fileName));
}
}
}
}

View file

@ -19,16 +19,20 @@ namespace Microsoft.Dotnet.Tools.Test.Tests
private const string Runtime = "some runtime";
private const int ParentProcessId = 1010;
private const int Port = 2314;
private const string TestRunner = "someTestRunner";
private const string PathToAssemblyUnderTest = "c:/some/path/assemblyUnderTest.dll";
private DotnetTestParams _dotnetTestFullParams;
private DotnetTestParams _emptyDotnetTestParams;
private readonly DotnetTestParams _dotnetTestFullParamsWithProjectJson;
private readonly DotnetTestParams _emptyDotnetTestParams;
private readonly DotnetTestParams _dotnetTestParamsWithAssembly;
public GivenThatWeWantToParseArgumentsForDotnetTest()
{
_dotnetTestFullParams = new DotnetTestParams();
_dotnetTestFullParamsWithProjectJson = new DotnetTestParams();
_dotnetTestParamsWithAssembly = new DotnetTestParams();
_emptyDotnetTestParams = new DotnetTestParams();
_dotnetTestFullParams.Parse(new[]
_dotnetTestFullParamsWithProjectJson.Parse(new[]
{
ProjectJson,
"--parentProcessId", ParentProcessId.ToString(),
@ -42,19 +46,25 @@ namespace Microsoft.Dotnet.Tools.Test.Tests
"--additional-parameters", "additional-parameter-value"
});
_dotnetTestParamsWithAssembly.Parse(new[]
{
PathToAssemblyUnderTest,
"--test-runner", TestRunner
});
_emptyDotnetTestParams.Parse(new string[] { });
}
[Fact]
public void It_sets_the_project_path_current_folder_if_one_is_not_passed_in()
{
_emptyDotnetTestParams.ProjectPath.Should().Be(Directory.GetCurrentDirectory());
_emptyDotnetTestParams.ProjectOrAssemblyPath.Should().Be(Directory.GetCurrentDirectory());
}
[Fact]
public void It_sets_the_project_path_to_the_passed_value()
{
_dotnetTestFullParams.ProjectPath.Should().Be(ProjectJson);
_dotnetTestFullParamsWithProjectJson.ProjectOrAssemblyPath.Should().Be(ProjectJson);
}
[Fact]
@ -72,7 +82,7 @@ namespace Microsoft.Dotnet.Tools.Test.Tests
[Fact]
public void It_converts_the_parent_process_id_to_int_when_a_valid_one_is_passed()
{
_dotnetTestFullParams.ParentProcessId.Should().Be(ParentProcessId);
_dotnetTestFullParamsWithProjectJson.ParentProcessId.Should().Be(ParentProcessId);
}
[Fact]
@ -96,7 +106,7 @@ namespace Microsoft.Dotnet.Tools.Test.Tests
[Fact]
public void It_converts_the_port_to_int_when_a_valid_one_is_passed()
{
_dotnetTestFullParams.Port.Should().Be(Port);
_dotnetTestFullParamsWithProjectJson.Port.Should().Be(Port);
}
[Fact]
@ -108,7 +118,7 @@ namespace Microsoft.Dotnet.Tools.Test.Tests
[Fact]
public void It_converts_the_framework_to_NugetFramework()
{
_dotnetTestFullParams.Framework.DotNetFrameworkName.Should().Be(".NETCoreApp,Version=v1.0");
_dotnetTestFullParamsWithProjectJson.Framework.DotNetFrameworkName.Should().Be(".NETCoreApp,Version=v1.0");
}
[Fact]
@ -129,7 +139,7 @@ namespace Microsoft.Dotnet.Tools.Test.Tests
[Fact]
public void It_sets_Output_when_one_is_passed_in()
{
_dotnetTestFullParams.Output.Should().Be(Output);
_dotnetTestFullParamsWithProjectJson.Output.Should().Be(Output);
}
[Fact]
@ -141,7 +151,7 @@ namespace Microsoft.Dotnet.Tools.Test.Tests
[Fact]
public void It_sets_BuildBasePath_when_one_is_passed_in()
{
_dotnetTestFullParams.BuildBasePath.Should().Be(Path.GetFullPath(BuildBasePath));
_dotnetTestFullParamsWithProjectJson.BuildBasePath.Should().Be(Path.GetFullPath(BuildBasePath));
}
[Fact]
@ -153,7 +163,7 @@ namespace Microsoft.Dotnet.Tools.Test.Tests
[Fact]
public void It_sets_Config_to_passed_in_value()
{
_dotnetTestFullParams.Config.Should().Be(Config);
_dotnetTestFullParamsWithProjectJson.Config.Should().Be(Config);
}
[Fact]
@ -165,7 +175,7 @@ namespace Microsoft.Dotnet.Tools.Test.Tests
[Fact]
public void It_sets_Runtime_when_one_is_passed_in()
{
_dotnetTestFullParams.Runtime.Should().Be(Runtime);
_dotnetTestFullParamsWithProjectJson.Runtime.Should().Be(Runtime);
}
[Fact]
@ -177,14 +187,14 @@ namespace Microsoft.Dotnet.Tools.Test.Tests
[Fact]
public void It_sets_any_remaining_params_to_RemainingArguments()
{
_dotnetTestFullParams.RemainingArguments.ShouldBeEquivalentTo(
_dotnetTestFullParamsWithProjectJson.RemainingArguments.ShouldBeEquivalentTo(
new [] { "--additional-parameters", "additional-parameter-value" });
}
[Fact]
public void It_sets_no_build_to_true_when_it_is_passed()
{
_dotnetTestFullParams.NoBuild.Should().BeTrue();
_dotnetTestFullParamsWithProjectJson.NoBuild.Should().BeTrue();
}
[Fact]
@ -196,7 +206,7 @@ namespace Microsoft.Dotnet.Tools.Test.Tests
[Fact]
public void It_sets_Help_to_false_when_help_is_not_passed_in()
{
_dotnetTestFullParams.Help.Should().BeFalse();
_dotnetTestFullParamsWithProjectJson.Help.Should().BeFalse();
}
[Fact]
@ -207,5 +217,65 @@ namespace Microsoft.Dotnet.Tools.Test.Tests
dotnetTestParams.Help.Should().BeTrue();
}
[Fact]
public void It_has_the_testRunner_null_by_default()
{
_emptyDotnetTestParams.TestRunner.Should().BeNull();
}
[Fact]
public void It_throws_when_you_specify_a_testRunner_along_with_a_folder()
{
var dotnetTestParams = new DotnetTestParams();
Action action = () => dotnetTestParams.Parse(new[]
{
"c:/some/path",
"--test-runner", "someTestRunner"
});
action
.ShouldThrow<InvalidOperationException>()
.WithMessage("You can only specify a test runner with a dll.");
}
[Fact]
public void It_throws_when_you_specify_a_testRunner_along_with_a_project_json()
{
var dotnetTestParams = new DotnetTestParams();
Action action = () => dotnetTestParams.Parse(new[]
{
ProjectJson,
"--test-runner", "someTestRunner"
});
action
.ShouldThrow<InvalidOperationException>()
.WithMessage("You can only specify a test runner with a dll.");
}
[Fact]
public void It_succeeds_when_specifying_an_assembly()
{
_dotnetTestParamsWithAssembly.ProjectOrAssemblyPath.Should().Be(PathToAssemblyUnderTest);
}
[Fact]
public void It_succeeds_when_specifying_a_test_runner_along_with_an_assembly()
{
_dotnetTestParamsWithAssembly.TestRunner.Should().Be(TestRunner);
}
[Fact]
public void When_a_testRunner_is_successfully_specified_then_HasTestRunner_returns_true()
{
_dotnetTestParamsWithAssembly.HasTestRunner.Should().BeTrue();
}
[Fact]
public void When_a_testRunner_is_noy_specified_then_HasTestRunner_returns_false()
{
_dotnetTestFullParamsWithProjectJson.HasTestRunner.Should().BeFalse();
}
}
}

View file

@ -6,7 +6,10 @@
"version": "1.0.0"
},
"Newtonsoft.Json": "9.0.1",
"dotnet": {
"Microsoft.DotNet.Tools.Test": {
"target": "project"
},
"Microsoft.DotNet.InternalAbstractions": {
"target": "project"
},
"Microsoft.Win32.Registry": {
@ -34,6 +37,7 @@
]
},
"buildOptions": {
"keyFile": "../../tools/test_key.snk",
"copyToOutput": {
"include": [
"../../TestAssets/TestProjects/ProjectsWithTests/NetCoreAppOnlyProject/project.json"