dotnet-installer/test/Microsoft.DotNet.ToolPackage.Tests/ToolPackageInstallerTests.cs
Peter Huene b9597913dc
Fix --source-feed option for tool install and update commands.
Commit 9cc2b7cd2f regressed the `--source-feed`
option so that it no longer accepted relative paths.  Because the option is now
saved to the temp project file, any relative paths specified by the
`--source-feed` option were made relative to the temp project path and not from
the current working directory of where dotnet was run.

The fix is to use `Path.GetFullPath` of the `--source-feed` option, provided
the option specified was not an absolute URI.

Fixes #9132.
2018-04-24 20:43:14 -07:00

788 lines
29 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.IO;
using System.Linq;
using System.Reflection;
using System.Transactions;
using FluentAssertions;
using Microsoft.DotNet.Tools.Test.Utilities;
using Microsoft.DotNet.Cli;
using Microsoft.DotNet.Cli.Utils;
using Microsoft.DotNet.Tools;
using Microsoft.DotNet.Tools.Tool.Install;
using Microsoft.DotNet.Tools.Tests.ComponentMocks;
using Microsoft.Extensions.DependencyModel.Tests;
using Microsoft.Extensions.EnvironmentAbstractions;
using NuGet.Versioning;
using Xunit;
namespace Microsoft.DotNet.ToolPackage.Tests
{
public class ToolPackageInstallerTests : TestBase
{
[Theory]
[InlineData(false)]
[InlineData(true)]
public void GivenNoFeedInstallFailsWithException(bool testMockBehaviorIsInSync)
{
var (store, installer, reporter, fileSystem) = Setup(
useMock: testMockBehaviorIsInSync,
feeds: new MockFeed[0]);
Action a = () => installer.InstallPackage(
packageId: TestPackageId,
versionRange: VersionRange.Parse(TestPackageVersion),
targetFramework: _testTargetframework);
a.ShouldThrow<ToolPackageException>().WithMessage(LocalizableStrings.ToolInstallationRestoreFailed);
reporter.Lines.Count.Should().Be(1);
reporter.Lines[0].Should().Contain(TestPackageId.ToString());
}
[Theory]
[InlineData(false)]
[InlineData(true)]
public void GivenOfflineFeedInstallSucceeds(bool testMockBehaviorIsInSync)
{
var (store, installer, reporter, fileSystem) = Setup(
useMock: testMockBehaviorIsInSync,
offlineFeed: new DirectoryPath(GetTestLocalFeedPath()),
feeds: GetOfflineMockFeed());
var package = installer.InstallPackage(
packageId: TestPackageId,
versionRange: VersionRange.Parse(TestPackageVersion),
targetFramework: _testTargetframework);
AssertPackageInstall(reporter, fileSystem, package, store);
package.Uninstall();
}
[Theory]
[InlineData(false)]
[InlineData(true)]
public void GivenAEmptySourceAndOfflineFeedInstallSucceeds(bool testMockBehaviorIsInSync)
{
var emptySource = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
Directory.CreateDirectory(emptySource);
var (store, installer, reporter, fileSystem) = Setup(
useMock: testMockBehaviorIsInSync,
offlineFeed: new DirectoryPath(GetTestLocalFeedPath()),
feeds: GetOfflineMockFeed());
var package = installer.InstallPackage(
packageId: TestPackageId,
versionRange: VersionRange.Parse(TestPackageVersion),
targetFramework: _testTargetframework,
additionalFeeds: new[] {emptySource});
AssertPackageInstall(reporter, fileSystem, package, store);
package.Uninstall();
}
[Theory]
[InlineData(false)]
[InlineData(true)]
public void GivenNugetConfigInstallSucceeds(bool testMockBehaviorIsInSync)
{
var nugetConfigPath = WriteNugetConfigFileToPointToTheFeed();
var (store, installer, reporter, fileSystem) = Setup(
useMock: testMockBehaviorIsInSync,
feeds: GetMockFeedsForConfigFile(nugetConfigPath));
var package = installer.InstallPackage(
packageId: TestPackageId,
versionRange: VersionRange.Parse(TestPackageVersion),
targetFramework: _testTargetframework,
nugetConfig: nugetConfigPath);
AssertPackageInstall(reporter, fileSystem, package, store);
package.Uninstall();
}
[Theory]
[InlineData(false)]
[InlineData(true)]
public void GivenNugetConfigInstallSucceedsInTransaction(bool testMockBehaviorIsInSync)
{
var nugetConfigPath = WriteNugetConfigFileToPointToTheFeed();
var (store, installer, reporter, fileSystem) = Setup(
useMock: testMockBehaviorIsInSync,
feeds: GetMockFeedsForConfigFile(nugetConfigPath));
IToolPackage package = null;
using (var transactionScope = new TransactionScope(
TransactionScopeOption.Required,
TimeSpan.Zero))
{
package = installer.InstallPackage(
packageId: TestPackageId,
versionRange: VersionRange.Parse(TestPackageVersion),
targetFramework: _testTargetframework,
nugetConfig: nugetConfigPath);
transactionScope.Complete();
}
AssertPackageInstall(reporter, fileSystem, package, store);
package.Uninstall();
}
[Theory]
[InlineData(false)]
[InlineData(true)]
public void GivenNugetConfigInstallCreatesAnAssetFile(bool testMockBehaviorIsInSync)
{
var nugetConfigPath = WriteNugetConfigFileToPointToTheFeed();
var (store, installer, reporter, fileSystem) = Setup(
useMock: testMockBehaviorIsInSync,
feeds: GetMockFeedsForConfigFile(nugetConfigPath));
var package = installer.InstallPackage(
packageId: TestPackageId,
versionRange: VersionRange.Parse(TestPackageVersion),
targetFramework: _testTargetframework,
nugetConfig: nugetConfigPath);
AssertPackageInstall(reporter, fileSystem, package, store);
/*
From mytool.dll to project.assets.json
<root>/packageid/version/packageid/version/tools/framework/rid/mytool.dll
/project.assets.json
*/
var assetJsonPath = package.Commands[0].Executable
.GetDirectoryPath()
.GetParentPath()
.GetParentPath()
.GetParentPath()
.GetParentPath()
.GetParentPath()
.WithFile("project.assets.json").Value;
fileSystem.File.Exists(assetJsonPath).Should().BeTrue();
package.Uninstall();
}
[Fact]
public void GivenAConfigFileRootDirectoryPackageInstallSucceeds()
{
var nugetConfigPath = WriteNugetConfigFileToPointToTheFeed();
var (store, installer, reporter, fileSystem) = Setup(useMock: false);
var package = installer.InstallPackage(
packageId: TestPackageId,
versionRange: VersionRange.Parse(TestPackageVersion),
targetFramework: _testTargetframework,
rootConfigDirectory: nugetConfigPath.GetDirectoryPath());
AssertPackageInstall(reporter, fileSystem, package, store);
package.Uninstall();
}
[Theory]
[InlineData(false)]
[InlineData(true)]
public void GivenAllButNoPackageVersionItCanInstallThePackage(bool testMockBehaviorIsInSync)
{
var nugetConfigPath = WriteNugetConfigFileToPointToTheFeed();
var (store, installer, reporter, fileSystem) = Setup(
useMock: testMockBehaviorIsInSync,
feeds: GetMockFeedsForConfigFile(nugetConfigPath));
var package = installer.InstallPackage(
packageId: TestPackageId,
targetFramework: _testTargetframework,
nugetConfig: nugetConfigPath);
AssertPackageInstall(reporter, fileSystem, package, store);
package.Uninstall();
}
[Theory]
[InlineData(false)]
[InlineData(true)]
public void GivenAllButNoTargetFrameworkItCanDownloadThePackage(bool testMockBehaviorIsInSync)
{
var nugetConfigPath = WriteNugetConfigFileToPointToTheFeed();
var (store, installer, reporter, fileSystem) = Setup(
useMock: testMockBehaviorIsInSync,
feeds: GetMockFeedsForConfigFile(nugetConfigPath));
var package = installer.InstallPackage(
packageId: TestPackageId,
versionRange: VersionRange.Parse(TestPackageVersion),
nugetConfig: nugetConfigPath);
AssertPackageInstall(reporter, fileSystem, package, store);
package.Uninstall();
}
[Theory]
[InlineData(false)]
[InlineData(true)]
public void GivenASourceInstallSucceeds(bool testMockBehaviorIsInSync)
{
var source = GetTestLocalFeedPath();
var (store, installer, reporter, fileSystem) = Setup(
useMock: testMockBehaviorIsInSync,
feeds: GetMockFeedsForSource(source));
var package = installer.InstallPackage(
packageId: TestPackageId,
versionRange: VersionRange.Parse(TestPackageVersion),
targetFramework: _testTargetframework,
additionalFeeds: new[] {source});
AssertPackageInstall(reporter, fileSystem, package, store);
package.Uninstall();
}
[Theory]
[InlineData(false)]
[InlineData(true)]
public void GivenARelativeSourcePathInstallSucceeds(bool testMockBehaviorIsInSync)
{
var source = GetTestLocalFeedPath();
var (store, installer, reporter, fileSystem) = Setup(
useMock: testMockBehaviorIsInSync,
feeds: GetMockFeedsForSource(source));
var package = installer.InstallPackage(
packageId: TestPackageId,
versionRange: VersionRange.Parse(TestPackageVersion),
targetFramework: _testTargetframework,
additionalFeeds: new[] { Path.GetRelativePath(Directory.GetCurrentDirectory(), source) });
AssertPackageInstall(reporter, fileSystem, package, store);
package.Uninstall();
}
[Theory]
[InlineData(false)]
[InlineData(true)]
public void GivenAUriSourceInstallSucceeds(bool testMockBehaviorIsInSync)
{
var source = GetTestLocalFeedPath();
var (store, installer, reporter, fileSystem) = Setup(
useMock: testMockBehaviorIsInSync,
feeds: GetMockFeedsForSource(source));
var package = installer.InstallPackage(
packageId: TestPackageId,
versionRange: VersionRange.Parse(TestPackageVersion),
targetFramework: _testTargetframework,
additionalFeeds: new[] { new Uri(source).AbsoluteUri });
AssertPackageInstall(reporter, fileSystem, package, store);
package.Uninstall();
}
[Theory]
[InlineData(false)]
[InlineData(true)]
public void GivenAEmptySourceAndNugetConfigInstallSucceeds(bool testMockBehaviorIsInSync)
{
var nugetConfigPath = WriteNugetConfigFileToPointToTheFeed();
var emptySource = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
Directory.CreateDirectory(emptySource);
var (store, installer, reporter, fileSystem) = Setup(
useMock: testMockBehaviorIsInSync,
feeds: GetMockFeedsForSource(emptySource));
var package = installer.InstallPackage(
packageId: TestPackageId,
versionRange: VersionRange.Parse(TestPackageVersion),
targetFramework: _testTargetframework,
nugetConfig: nugetConfigPath, additionalFeeds: new[] {emptySource});
AssertPackageInstall(reporter, fileSystem, package, store);
package.Uninstall();
}
[Theory]
[InlineData(false)]
[InlineData(true)]
public void GivenFailedRestoreInstallWillRollback(bool testMockBehaviorIsInSync)
{
var (store, installer, reporter, fileSystem) = Setup(
useMock: testMockBehaviorIsInSync);
Action a = () => {
using (var t = new TransactionScope(
TransactionScopeOption.Required,
TimeSpan.Zero))
{
installer.InstallPackage(new PackageId("non.existent.package.id"));
t.Complete();
}
};
a.ShouldThrow<ToolPackageException>().WithMessage(LocalizableStrings.ToolInstallationRestoreFailed);
AssertInstallRollBack(fileSystem, store);
}
[Theory]
[InlineData(false)]
[InlineData(true)]
public void GivenFailureAfterRestoreInstallWillRollback(bool testMockBehaviorIsInSync)
{
var source = GetTestLocalFeedPath();
var (store, installer, reporter, fileSystem) = Setup(
useMock: testMockBehaviorIsInSync,
feeds: GetMockFeedsForSource(source));
void FailedStepAfterSuccessRestore() => throw new GracefulException("simulated error");
Action a = () => {
using (var t = new TransactionScope(
TransactionScopeOption.Required,
TimeSpan.Zero))
{
installer.InstallPackage(
packageId: TestPackageId,
versionRange: VersionRange.Parse(TestPackageVersion),
targetFramework: _testTargetframework,
additionalFeeds: new[] {source});
FailedStepAfterSuccessRestore();
t.Complete();
}
};
a.ShouldThrow<GracefulException>().WithMessage("simulated error");
AssertInstallRollBack(fileSystem, store);
}
[Theory]
[InlineData(false)]
[InlineData(true)]
public void GivenSecondInstallInATransactionTheFirstInstallShouldRollback(bool testMockBehaviorIsInSync)
{
var source = GetTestLocalFeedPath();
var (store, installer, reporter, fileSystem) = Setup(
useMock: testMockBehaviorIsInSync,
feeds: GetMockFeedsForSource(source));
Action a = () => {
using (var t = new TransactionScope(
TransactionScopeOption.Required,
TimeSpan.Zero))
{
Action first = () => installer.InstallPackage(
packageId: TestPackageId,
versionRange: VersionRange.Parse(TestPackageVersion),
targetFramework: _testTargetframework,
additionalFeeds: new[] {source});
first.ShouldNotThrow();
installer.InstallPackage(
packageId: TestPackageId,
versionRange: VersionRange.Parse(TestPackageVersion),
targetFramework: _testTargetframework,
additionalFeeds: new[] {source});
t.Complete();
}
};
a.ShouldThrow<ToolPackageException>().Where(
ex => ex.Message ==
string.Format(
CommonLocalizableStrings.ToolPackageConflictPackageId,
TestPackageId,
TestPackageVersion));
AssertInstallRollBack(fileSystem, store);
}
[Theory]
[InlineData(false)]
[InlineData(true)]
public void GivenSecondInstallWithoutATransactionTheFirstShouldNotRollback(bool testMockBehaviorIsInSync)
{
var source = GetTestLocalFeedPath();
var (store, installer, reporter, fileSystem) = Setup(
useMock: testMockBehaviorIsInSync,
feeds: GetMockFeedsForSource(source));
var package = installer.InstallPackage(
packageId: TestPackageId,
versionRange: VersionRange.Parse(TestPackageVersion),
targetFramework: _testTargetframework,
additionalFeeds: new[] {source});
AssertPackageInstall(reporter, fileSystem, package, store);
Action secondCall = () => installer.InstallPackage(
packageId: TestPackageId,
versionRange: VersionRange.Parse(TestPackageVersion),
targetFramework: _testTargetframework,
additionalFeeds: new[] {source});
reporter.Lines.Should().BeEmpty();
secondCall.ShouldThrow<ToolPackageException>().Where(
ex => ex.Message ==
string.Format(
CommonLocalizableStrings.ToolPackageConflictPackageId,
TestPackageId,
TestPackageVersion));
fileSystem
.Directory
.Exists(store.Root.WithSubDirectories(TestPackageId.ToString()).Value)
.Should()
.BeTrue();
package.Uninstall();
fileSystem
.Directory
.EnumerateFileSystemEntries(store.Root.WithSubDirectories(ToolPackageStore.StagingDirectory).Value)
.Should()
.BeEmpty();
}
[Theory]
[InlineData(false)]
[InlineData(true)]
public void GivenAnInstalledPackageUninstallRemovesThePackage(bool testMockBehaviorIsInSync)
{
var source = GetTestLocalFeedPath();
var (store, installer, reporter, fileSystem) = Setup(
useMock: testMockBehaviorIsInSync,
feeds: GetMockFeedsForSource(source));
var package = installer.InstallPackage(
packageId: TestPackageId,
versionRange: VersionRange.Parse(TestPackageVersion),
targetFramework: _testTargetframework,
additionalFeeds: new[] {source});
AssertPackageInstall(reporter, fileSystem, package, store);
package.Uninstall();
store.EnumeratePackages().Should().BeEmpty();
}
[Theory]
[InlineData(false)]
[InlineData(true)]
public void GivenAnInstalledPackageUninstallRollsbackWhenTransactionFails(bool testMockBehaviorIsInSync)
{
var source = GetTestLocalFeedPath();
var (store, installer, reporter, fileSystem) = Setup(
useMock: testMockBehaviorIsInSync,
feeds: GetMockFeedsForSource(source));
var package = installer.InstallPackage(
packageId: TestPackageId,
versionRange: VersionRange.Parse(TestPackageVersion),
targetFramework: _testTargetframework,
additionalFeeds: new[] {source});
AssertPackageInstall(reporter, fileSystem, package, store);
using (var scope = new TransactionScope(
TransactionScopeOption.Required,
TimeSpan.Zero))
{
package.Uninstall();
store.EnumeratePackages().Should().BeEmpty();
}
package = store.EnumeratePackageVersions(TestPackageId).First();
AssertPackageInstall(reporter, fileSystem, package, store);
}
[Theory]
[InlineData(false)]
[InlineData(true)]
public void GivenAnInstalledPackageUninstallRemovesThePackageWhenTransactionCommits(bool testMockBehaviorIsInSync)
{
var source = GetTestLocalFeedPath();
var (store, installer, reporter, fileSystem) = Setup(
useMock: testMockBehaviorIsInSync,
feeds: GetMockFeedsForSource(source));
var package = installer.InstallPackage(
packageId: TestPackageId,
versionRange: VersionRange.Parse(TestPackageVersion),
targetFramework: _testTargetframework,
additionalFeeds: new[] {source});
AssertPackageInstall(reporter, fileSystem, package, store);
using (var scope = new TransactionScope(
TransactionScopeOption.Required,
TimeSpan.Zero))
{
package.Uninstall();
scope.Complete();
}
store.EnumeratePackages().Should().BeEmpty();
}
[Theory]
[InlineData(false)]
[InlineData(true)]
public void GivenAPackageNameWithDifferentCaseItCanInstallThePackage(bool testMockBehaviorIsInSync)
{
var nugetConfigPath = WriteNugetConfigFileToPointToTheFeed();
var (store, installer, reporter, fileSystem) = Setup(
useMock: testMockBehaviorIsInSync,
feeds: GetMockFeedsForConfigFile(nugetConfigPath));
var package = installer.InstallPackage(
packageId: new PackageId("GlObAl.TooL.coNsoLe.DemO"),
targetFramework: _testTargetframework,
nugetConfig: nugetConfigPath);
AssertPackageInstall(reporter, fileSystem, package, store);
package.Uninstall();
}
[Fact]
public void GivenANuGetDiagnosticMessageItShouldNotContainTheTempProject()
{
var nugetConfigPath = WriteNugetConfigFileToPointToTheFeed();
var tempProject = GetUniqueTempProjectPathEachTest();
var (store, installer, reporter, fileSystem) = Setup(
useMock: false,
tempProject: tempProject);
var package = installer.InstallPackage(
packageId: TestPackageId,
versionRange: VersionRange.Parse("1.0.*"),
targetFramework: _testTargetframework,
nugetConfig: nugetConfigPath);
reporter.Lines.Should().NotBeEmpty();
reporter.Lines.Should().Contain(l => l.Contains("warning"));
reporter.Lines.Should().NotContain(l => l.Contains(tempProject.Value));
reporter.Lines.Clear();
AssertPackageInstall(reporter, fileSystem, package, store);
package.Uninstall();
}
private static void AssertPackageInstall(
BufferedReporter reporter,
IFileSystem fileSystem,
IToolPackage package,
IToolPackageStore store)
{
reporter.Lines.Should().BeEmpty();
package.Id.Should().Be(TestPackageId);
package.Version.ToNormalizedString().Should().Be(TestPackageVersion);
package.PackageDirectory.Value.Should().Contain(store.Root.Value);
store.EnumeratePackageVersions(TestPackageId)
.Select(p => p.Version.ToNormalizedString())
.Should()
.Equal(TestPackageVersion);
package.Commands.Count.Should().Be(1);
fileSystem.File.Exists(package.Commands[0].Executable.Value).Should().BeTrue($"{package.Commands[0].Executable.Value} should exist");
package.Commands[0].Executable.Value.Should().Contain(store.Root.Value);
}
private static void AssertInstallRollBack(IFileSystem fileSystem, IToolPackageStore store)
{
if (!fileSystem.Directory.Exists(store.Root.Value))
{
return;
}
fileSystem
.Directory
.EnumerateFileSystemEntries(store.Root.Value)
.Should()
.NotContain(e => Path.GetFileName(e) != ToolPackageStore.StagingDirectory);
fileSystem
.Directory
.EnumerateFileSystemEntries(store.Root.WithSubDirectories(ToolPackageStore.StagingDirectory).Value)
.Should()
.BeEmpty();
}
private static FilePath GetUniqueTempProjectPathEachTest()
{
var tempProjectDirectory =
new DirectoryPath(Path.GetTempPath()).WithSubDirectories(Path.GetRandomFileName());
var tempProjectPath =
tempProjectDirectory.WithFile(Path.GetRandomFileName() + ".csproj");
return tempProjectPath;
}
private static IEnumerable<MockFeed> GetMockFeedsForConfigFile(FilePath nugetConfig)
{
return new MockFeed[]
{
new MockFeed
{
Type = MockFeedType.ExplicitNugetConfig,
Uri = nugetConfig.Value,
Packages = new List<MockFeedPackage>
{
new MockFeedPackage
{
PackageId = TestPackageId.ToString(),
Version = TestPackageVersion
}
}
}
};
}
private static IEnumerable<MockFeed> GetMockFeedsForSource(string source)
{
return new MockFeed[]
{
new MockFeed
{
Type = MockFeedType.ImplicitAdditionalFeed,
Uri = source,
Packages = new List<MockFeedPackage>
{
new MockFeedPackage
{
PackageId = TestPackageId.ToString(),
Version = TestPackageVersion
}
}
}
};
}
private static IEnumerable<MockFeed> GetOfflineMockFeed()
{
return new MockFeed[]
{
new MockFeed
{
Type = MockFeedType.ImplicitAdditionalFeed,
Uri = GetTestLocalFeedPath(),
Packages = new List<MockFeedPackage>
{
new MockFeedPackage
{
PackageId = TestPackageId.ToString(),
Version = TestPackageVersion
}
}
}
};
}
private static (IToolPackageStore, IToolPackageInstaller, BufferedReporter, IFileSystem) Setup(
bool useMock,
IEnumerable<MockFeed> feeds = null,
FilePath? tempProject = null,
DirectoryPath? offlineFeed = null)
{
var root = new DirectoryPath(Path.Combine(TempRoot.Root, Path.GetRandomFileName()));
var reporter = new BufferedReporter();
IFileSystem fileSystem;
IToolPackageStore store;
IToolPackageInstaller installer;
if (useMock)
{
fileSystem = new FileSystemMockBuilder().Build();
store = new ToolPackageStoreMock(root, fileSystem);
installer = new ToolPackageInstallerMock(
fileSystem: fileSystem,
store: store,
projectRestorer: new ProjectRestorerMock(
fileSystem: fileSystem,
reporter: reporter,
feeds: feeds));
}
else
{
fileSystem = new FileSystemWrapper();
store = new ToolPackageStore(root);
installer = new ToolPackageInstaller(
store: store,
projectRestorer: new ProjectRestorer(reporter),
tempProject: tempProject ?? GetUniqueTempProjectPathEachTest(),
offlineFeed: offlineFeed ?? new DirectoryPath("does not exist"));
}
store.Root.Value.Should().Be(Path.GetFullPath(root.Value));
return (store, installer, reporter, fileSystem);
}
private static FilePath WriteNugetConfigFileToPointToTheFeed()
{
var nugetConfigName = "nuget.config";
var tempPathForNugetConfigWithWhiteSpace =
Path.Combine(Path.GetTempPath(),
Path.GetRandomFileName() + " " + Path.GetRandomFileName());
Directory.CreateDirectory(tempPathForNugetConfigWithWhiteSpace);
NuGetConfig.Write(
directory: tempPathForNugetConfigWithWhiteSpace,
configname: nugetConfigName,
localFeedPath: GetTestLocalFeedPath());
return new FilePath(Path.GetFullPath(Path.Combine(tempPathForNugetConfigWithWhiteSpace, nugetConfigName)));
}
private static string GetTestLocalFeedPath() => Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "TestAssetLocalNugetFeed");
private readonly string _testTargetframework = BundledTargetFramework.GetTargetFrameworkMoniker();
private const string TestPackageVersion = "1.0.4";
private static readonly PackageId TestPackageId = new PackageId("global.tool.console.demo");
}
}