441 lines
16 KiB
C#
441 lines
16 KiB
C#
// 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.Diagnostics;
|
|
using System.IO;
|
|
using System.Runtime.InteropServices;
|
|
using FluentAssertions;
|
|
using Microsoft.DotNet.Cli.Utils;
|
|
using Microsoft.DotNet.TestFramework;
|
|
using Microsoft.DotNet.Tools.Test.Utilities;
|
|
using Microsoft.DotNet.InternalAbstractions;
|
|
using Xunit;
|
|
using Xunit.Abstractions;
|
|
using Microsoft.Build.Construction;
|
|
using System.Linq;
|
|
using Microsoft.Build.Evaluation;
|
|
|
|
namespace Microsoft.DotNet.Tests
|
|
{
|
|
public class PackagedCommandTests : TestBase
|
|
{
|
|
private readonly ITestOutputHelper _output;
|
|
|
|
public PackagedCommandTests(ITestOutputHelper output)
|
|
{
|
|
_output = output;
|
|
}
|
|
|
|
public static IEnumerable<object[]> DependencyToolArguments
|
|
{
|
|
get
|
|
{
|
|
var rid = DotnetLegacyRuntimeIdentifiers.InferLegacyRestoreRuntimeIdentifier();
|
|
var projectOutputPath = $"AppWithProjTool2Fx\\bin\\Debug\\net451\\{rid}\\dotnet-desktop-and-portable.exe";
|
|
return new[]
|
|
{
|
|
new object[] { "CoreFX", ".NETCoreApp,Version=v1.0", "lib\\netcoreapp1.0\\dotnet-desktop-and-portable.dll" },
|
|
new object[] { "NetFX", ".NETFramework,Version=v4.5.1", projectOutputPath }
|
|
};
|
|
}
|
|
}
|
|
public static IEnumerable<object[]> LibraryDependencyToolArguments
|
|
{
|
|
get
|
|
{
|
|
var rid = DotnetLegacyRuntimeIdentifiers.InferLegacyRestoreRuntimeIdentifier();
|
|
|
|
var projectOutputPath = $"LibWithProjTool2Fx\\bin\\Debug\\net451\\dotnet-desktop-and-portable.exe";
|
|
|
|
return new[]
|
|
{
|
|
new object[] { "CoreFX", ".NETStandard,Version=v1.6", "lib\\netstandard1.6\\dotnet-desktop-and-portable.dll" },
|
|
new object[] { "NetFX", ".NETFramework,Version=v4.5.1", projectOutputPath }
|
|
};
|
|
}
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("AppWithDirectAndToolDep", true)]
|
|
[InlineData("AppWithToolDependency", false)]
|
|
public void TestProjectToolIsAvailableThroughDriver(string appName, bool useCurrentFrameworkRuntimeVersion)
|
|
{
|
|
var testInstance = TestAssets.Get(appName)
|
|
.CreateInstance()
|
|
.WithSourceFiles()
|
|
.WithNuGetConfig(new RepoDirectoriesProvider().TestPackages);
|
|
|
|
if (useCurrentFrameworkRuntimeVersion)
|
|
{
|
|
testInstance = testInstance.UseCurrentRuntimeFrameworkVersion();
|
|
}
|
|
|
|
// restore again now that the project has changed
|
|
new RestoreCommand()
|
|
.WithWorkingDirectory(testInstance.Root)
|
|
.Execute()
|
|
.Should().Pass();
|
|
|
|
new BuildCommand()
|
|
.WithProjectDirectory(testInstance.Root)
|
|
.Execute()
|
|
.Should().Pass();
|
|
|
|
new PortableCommand()
|
|
.WithWorkingDirectory(testInstance.Root)
|
|
.ExecuteWithCapturedOutput()
|
|
.Should().HaveStdOutContaining("Hello Portable World!")
|
|
.And.NotHaveStdErr()
|
|
.And.Pass();
|
|
}
|
|
|
|
[Fact]
|
|
public void CanInvokeToolWhosePackageNameIsDifferentFromDllName()
|
|
{
|
|
var testInstance = TestAssets.Get("AppWithDepOnToolWithOutputName")
|
|
.CreateInstance()
|
|
.WithSourceFiles()
|
|
.WithRestoreFiles();
|
|
|
|
new BuildCommand()
|
|
.WithProjectDirectory(testInstance.Root)
|
|
.Execute()
|
|
.Should().Pass();
|
|
|
|
new GenericCommand("tool-with-output-name")
|
|
.WithWorkingDirectory(testInstance.Root)
|
|
.ExecuteWithCapturedOutput()
|
|
.Should().HaveStdOutContaining("Tool with output name!")
|
|
.And.NotHaveStdErr()
|
|
.And.Pass();
|
|
}
|
|
|
|
[Fact]
|
|
public void CanInvokeToolFromDirectDependenciesIfPackageNameDifferentFromToolName()
|
|
{
|
|
var testInstance = TestAssets.Get("AppWithDirectDepWithOutputName")
|
|
.CreateInstance()
|
|
.WithSourceFiles()
|
|
.WithRestoreFiles();
|
|
|
|
const string framework = ".NETCoreApp,Version=v2.0";
|
|
|
|
new BuildCommand()
|
|
.WithProjectDirectory(testInstance.Root)
|
|
.WithConfiguration("Debug")
|
|
.Execute()
|
|
.Should().Pass();
|
|
|
|
new DependencyToolInvokerCommand()
|
|
.WithWorkingDirectory(testInstance.Root)
|
|
.WithEnvironmentVariable(CommandContext.Variables.Verbose, "true")
|
|
.ExecuteWithCapturedOutput($"tool-with-output-name", framework, "")
|
|
.Should().HaveStdOutContaining("Tool with output name!")
|
|
.And.NotHaveStdErr()
|
|
.And.Pass();
|
|
}
|
|
|
|
[Fact]
|
|
public void ItShowsErrorWhenToolIsNotRestored()
|
|
{
|
|
var testInstance = TestAssets.Get("NonRestoredTestProjects", "AppWithNonExistingToolDependency")
|
|
.CreateInstance()
|
|
.WithSourceFiles();
|
|
|
|
new TestCommand("dotnet")
|
|
.WithWorkingDirectory(testInstance.Root)
|
|
.ExecuteWithCapturedOutput("nonexistingtool")
|
|
.Should().Fail()
|
|
.And.HaveStdErrContaining("No executable found matching command \"dotnet-nonexistingtool\"");
|
|
}
|
|
|
|
[Fact]
|
|
public void ItRunsToolRestoredToSpecificPackageDir()
|
|
{
|
|
var testInstance = TestAssets.Get("NonRestoredTestProjects", "ToolWithRandomPackageName")
|
|
.CreateInstance()
|
|
.WithSourceFiles();
|
|
|
|
var appWithDepOnToolDir = testInstance.Root.Sub("AppWithDepOnTool");
|
|
var toolWithRandPkgNameDir = testInstance.Root.Sub("ToolWithRandomPackageName");
|
|
var pkgsDir = testInstance.Root.CreateSubdirectory("pkgs");
|
|
|
|
// 3ebdd4f1-a194-470a-b01a-4515672791d1
|
|
// ^-- index = 24
|
|
string randomPackageName = Guid.NewGuid().ToString().Substring(24);
|
|
|
|
// TODO: This is a workround for https://github.com/dotnet/cli/issues/5020
|
|
SetGeneratedPackageName(appWithDepOnToolDir.GetFile("AppWithDepOnTool.csproj"),
|
|
randomPackageName);
|
|
|
|
SetGeneratedPackageName(toolWithRandPkgNameDir.GetFile("ToolWithRandomPackageName.csproj"),
|
|
randomPackageName);
|
|
|
|
new RestoreCommand()
|
|
.WithWorkingDirectory(toolWithRandPkgNameDir)
|
|
.Execute()
|
|
.Should().Pass();
|
|
|
|
new PackCommand()
|
|
.WithWorkingDirectory(toolWithRandPkgNameDir)
|
|
.Execute($"-o \"{pkgsDir.FullName}\"")
|
|
.Should().Pass();
|
|
|
|
new RestoreCommand()
|
|
.WithWorkingDirectory(appWithDepOnToolDir)
|
|
.Execute($"--packages \"{pkgsDir.FullName}\"")
|
|
.Should().Pass();
|
|
|
|
new TestCommand("dotnet")
|
|
.WithWorkingDirectory(appWithDepOnToolDir)
|
|
.ExecuteWithCapturedOutput("randompackage")
|
|
.Should().Pass()
|
|
.And.HaveStdOutContaining("Hello World from tool!")
|
|
.And.NotHaveStdErr();
|
|
}
|
|
|
|
[WindowsOnlyTheory(Skip="https://github.com/dotnet/cli/issues/4514")]
|
|
[MemberData("DependencyToolArguments")]
|
|
public void TestFrameworkSpecificDependencyToolsCanBeInvoked(string identifier, string framework, string expectedDependencyToolPath)
|
|
{
|
|
var testInstance = TestAssets.Get(TestAssetKinds.DesktopTestProjects, "AppWithProjTool2Fx")
|
|
.CreateInstance(identifier: identifier)
|
|
.WithSourceFiles()
|
|
.WithRestoreFiles();
|
|
|
|
new BuildCommand()
|
|
.WithWorkingDirectory(testInstance.Root)
|
|
.WithConfiguration("Debug")
|
|
.Execute()
|
|
.Should().Pass();
|
|
|
|
new DependencyToolInvokerCommand()
|
|
.WithWorkingDirectory(testInstance.Root)
|
|
.ExecuteWithCapturedOutput($"desktop-and-portable {framework} {identifier}")
|
|
.Should().HaveStdOutContaining(framework)
|
|
.And.HaveStdOutContaining(identifier)
|
|
.And.HaveStdOutContaining(expectedDependencyToolPath)
|
|
.And.NotHaveStdErr()
|
|
.And.Pass();
|
|
}
|
|
|
|
[WindowsOnlyTheory]
|
|
[MemberData("LibraryDependencyToolArguments")]
|
|
public void TestFrameworkSpecificLibraryDependencyToolsCannotBeInvoked(string identifier, string framework, string expectedDependencyToolPath)
|
|
{
|
|
var testInstance = TestAssets.Get(TestAssetKinds.DesktopTestProjects, "LibWithProjTool2Fx")
|
|
.CreateInstance(identifier: identifier)
|
|
.WithSourceFiles()
|
|
.WithRestoreFiles();
|
|
|
|
new BuildCommand()
|
|
.WithWorkingDirectory(testInstance.Root)
|
|
.WithConfiguration("Debug")
|
|
.Execute()
|
|
.Should().Pass();
|
|
|
|
new DependencyToolInvokerCommand()
|
|
.WithWorkingDirectory(testInstance.Root)
|
|
.ExecuteWithCapturedOutput($"desktop-and-portable {framework} {identifier}")
|
|
.Should().Fail();
|
|
}
|
|
|
|
[Fact]
|
|
public void ToolsCanAccessDependencyContextProperly()
|
|
{
|
|
var testInstance = TestAssets.Get("DependencyContextFromTool")
|
|
.CreateInstance()
|
|
.WithSourceFiles()
|
|
.WithRestoreFiles();
|
|
|
|
new DependencyContextTestCommand()
|
|
.WithWorkingDirectory(testInstance.Root)
|
|
.Execute("")
|
|
.Should().Pass();
|
|
}
|
|
|
|
[Fact]
|
|
public void TestProjectDependencyIsNotAvailableThroughDriver()
|
|
{
|
|
var testInstance = TestAssets.Get("AppWithDirectDep")
|
|
.CreateInstance()
|
|
.WithSourceFiles()
|
|
.UseCurrentRuntimeFrameworkVersion()
|
|
.WithNuGetConfig(new RepoDirectoriesProvider().TestPackages);
|
|
|
|
// restore again now that the project has changed
|
|
new RestoreCommand()
|
|
.WithWorkingDirectory(testInstance.Root)
|
|
.Execute()
|
|
.Should().Pass();
|
|
|
|
new BuildCommand()
|
|
.WithWorkingDirectory(testInstance.Root)
|
|
.Execute()
|
|
.Should().Pass();
|
|
|
|
var currentDirectory = Directory.GetCurrentDirectory();
|
|
|
|
CommandResult result = new HelloCommand()
|
|
.WithWorkingDirectory(testInstance.Root)
|
|
.ExecuteWithCapturedOutput();
|
|
|
|
result.StdErr.Should().Contain("No executable found matching command");
|
|
|
|
result.Should().Fail();
|
|
}
|
|
|
|
[Fact]
|
|
public void WhenToolAssetsFileIsInUseThenCLIRetriesLaunchingTheCommandForAtLeastOneSecond()
|
|
{
|
|
var testInstance = TestAssets.Get("AppWithToolDependency")
|
|
.CreateInstance()
|
|
.WithSourceFiles()
|
|
.WithRestoreFiles();
|
|
|
|
var assetsFile = new DirectoryInfo(new RepoDirectoriesProvider().NugetPackages)
|
|
.GetDirectory(".tools", "dotnet-portable", "1.0.0", "netcoreapp1.0")
|
|
.GetFile("project.assets.json");
|
|
|
|
var stopWatch = Stopwatch.StartNew();
|
|
|
|
using (assetsFile.Lock()
|
|
.DisposeAfter(TimeSpan.FromMilliseconds(1000)))
|
|
{
|
|
new PortableCommand()
|
|
.WithWorkingDirectory(testInstance.Root)
|
|
.ExecuteWithCapturedOutput()
|
|
.Should().HaveStdOutContaining("Hello Portable World!")
|
|
.And.NotHaveStdErr()
|
|
.And.Pass();
|
|
}
|
|
|
|
stopWatch.Stop();
|
|
|
|
stopWatch.ElapsedMilliseconds.Should().BeGreaterThan(1000, "Because dotnet should respect the NuGet lock");
|
|
}
|
|
|
|
[Fact]
|
|
public void WhenToolAssetsFileIsLockedByNuGetThenCLIRetriesLaunchingTheCommandForAtLeastOneSecond()
|
|
{
|
|
var testInstance = TestAssets.Get("AppWithToolDependency")
|
|
.CreateInstance()
|
|
.WithSourceFiles()
|
|
.WithRestoreFiles();
|
|
|
|
var assetsFile = new DirectoryInfo(new RepoDirectoriesProvider().NugetPackages)
|
|
.GetDirectory(".tools", "dotnet-portable", "1.0.0", "netcoreapp1.0")
|
|
.GetFile("project.assets.json");
|
|
|
|
var stopWatch = Stopwatch.StartNew();
|
|
|
|
using (assetsFile.NuGetLock()
|
|
.DisposeAfter(TimeSpan.FromMilliseconds(1000)))
|
|
{
|
|
new PortableCommand()
|
|
.WithWorkingDirectory(testInstance.Root)
|
|
.ExecuteWithCapturedOutput()
|
|
.Should().HaveStdOutContaining("Hello Portable World!")
|
|
.And.NotHaveStdErr()
|
|
.And.Pass();
|
|
}
|
|
|
|
stopWatch.Stop();
|
|
|
|
stopWatch.ElapsedMilliseconds.Should().BeGreaterThan(1000, "Because dotnet should respect the NuGet lock");
|
|
}
|
|
|
|
private void SetGeneratedPackageName(FileInfo project, string packageName)
|
|
{
|
|
const string propertyName = "GeneratedPackageId";
|
|
var p = ProjectRootElement.Open(project.FullName, new ProjectCollection(), true);
|
|
p.AddProperty(propertyName, packageName);
|
|
p.Save();
|
|
}
|
|
|
|
class HelloCommand : TestCommand
|
|
{
|
|
public HelloCommand()
|
|
: base("dotnet")
|
|
{
|
|
}
|
|
|
|
public override CommandResult Execute(string args = "")
|
|
{
|
|
args = $"hello {args}";
|
|
return base.Execute(args);
|
|
}
|
|
|
|
public override CommandResult ExecuteWithCapturedOutput(string args = "")
|
|
{
|
|
args = $"hello {args}";
|
|
return base.ExecuteWithCapturedOutput(args);
|
|
}
|
|
}
|
|
|
|
class PortableCommand : TestCommand
|
|
{
|
|
public PortableCommand()
|
|
: base("dotnet")
|
|
{
|
|
}
|
|
|
|
public override CommandResult Execute(string args = "")
|
|
{
|
|
args = $"portable {args}";
|
|
return base.Execute(args);
|
|
}
|
|
|
|
public override CommandResult ExecuteWithCapturedOutput(string args = "")
|
|
{
|
|
args = $"portable {args}";
|
|
return base.ExecuteWithCapturedOutput(args);
|
|
}
|
|
}
|
|
|
|
class GenericCommand : TestCommand
|
|
{
|
|
private readonly string _commandName;
|
|
|
|
public GenericCommand(string commandName)
|
|
: base("dotnet")
|
|
{
|
|
_commandName = commandName;
|
|
}
|
|
|
|
public override CommandResult Execute(string args = "")
|
|
{
|
|
args = $"{_commandName} {args}";
|
|
return base.Execute(args);
|
|
}
|
|
|
|
public override CommandResult ExecuteWithCapturedOutput(string args = "")
|
|
{
|
|
args = $"{_commandName} {args}";
|
|
return base.ExecuteWithCapturedOutput(args);
|
|
}
|
|
}
|
|
|
|
class DependencyContextTestCommand : TestCommand
|
|
{
|
|
public DependencyContextTestCommand()
|
|
: base("dotnet")
|
|
{
|
|
}
|
|
|
|
public override CommandResult Execute(string path)
|
|
{
|
|
var args = $"dependency-context-test {path}";
|
|
return base.Execute(args);
|
|
}
|
|
|
|
public override CommandResult ExecuteWithCapturedOutput(string path)
|
|
{
|
|
var args = $"dependency-context-test {path}";
|
|
return base.ExecuteWithCapturedOutput(args);
|
|
}
|
|
}
|
|
}
|
|
}
|