Atomic install tool (#8518)
* Make dotnet install tool atomic Apply TransactionScope to tool install. It can handle the correct timing of roll back and commit. Convert existing ToolPackageObtainer and ShellShimMaker by passing logic via lambda to an object that has IEnlistmentNotification interface. It turns out the very clean. Use .stage as staging place to verify of package content, and shim. It should roll back when something is wrong. When there is ctrl-c, there will be garbage in .stage folder but not the root of the package folder.
This commit is contained in:
parent
38e452204c
commit
5fa558a2ed
33 changed files with 995 additions and 207 deletions
|
@ -47,5 +47,10 @@ namespace Microsoft.Extensions.EnvironmentAbstractions
|
||||||
{
|
{
|
||||||
Directory.CreateDirectory(path);
|
Directory.CreateDirectory(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Delete(string path, bool recursive)
|
||||||
|
{
|
||||||
|
Directory.Delete(path, recursive);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,5 +45,10 @@ namespace Microsoft.Extensions.EnvironmentAbstractions
|
||||||
{
|
{
|
||||||
File.WriteAllText(path, content);
|
File.WriteAllText(path, content);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Delete(string path)
|
||||||
|
{
|
||||||
|
File.Delete(path);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,5 +16,7 @@ namespace Microsoft.Extensions.EnvironmentAbstractions
|
||||||
string GetDirectoryFullName(string path);
|
string GetDirectoryFullName(string path);
|
||||||
|
|
||||||
void CreateDirectory(string path);
|
void CreateDirectory(string path);
|
||||||
|
|
||||||
|
void Delete(string path, bool recursive);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,5 +24,7 @@ namespace Microsoft.Extensions.EnvironmentAbstractions
|
||||||
void CreateEmptyFile(string path);
|
void CreateEmptyFile(string path);
|
||||||
|
|
||||||
void WriteAllText(string path, string content);
|
void WriteAllText(string path, string content);
|
||||||
|
|
||||||
|
void Delete(string path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -588,4 +588,7 @@ Output: {1}</value>
|
||||||
<data name="ToolPackageMissingSettingsFile" xml:space="preserve">
|
<data name="ToolPackageMissingSettingsFile" xml:space="preserve">
|
||||||
<value>Package '{0}' is missing tool settings file DotnetToolSettings.xml.</value>
|
<value>Package '{0}' is missing tool settings file DotnetToolSettings.xml.</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="ToolPackageConflictPackageId" xml:space="preserve">
|
||||||
|
<value>Tool '{0}' is already installed.</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
52
src/dotnet/ShellShim/CreateShimTransaction.cs
Normal file
52
src/dotnet/ShellShim/CreateShimTransaction.cs
Normal 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;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Transactions;
|
||||||
|
using Microsoft.Extensions.EnvironmentAbstractions;
|
||||||
|
|
||||||
|
namespace Microsoft.DotNet.ShellShim
|
||||||
|
{
|
||||||
|
internal class CreateShimTransaction : IEnlistmentNotification
|
||||||
|
{
|
||||||
|
private readonly Action<List<FilePath>> _createShim;
|
||||||
|
private readonly Action<List<FilePath>> _rollback;
|
||||||
|
private List<FilePath> _locationOfShimDuringTransaction = new List<FilePath>();
|
||||||
|
|
||||||
|
public CreateShimTransaction(
|
||||||
|
Action<List<FilePath>> createShim,
|
||||||
|
Action<List<FilePath>> rollback)
|
||||||
|
{
|
||||||
|
_createShim = createShim ?? throw new ArgumentNullException(nameof(createShim));
|
||||||
|
_rollback = rollback ?? throw new ArgumentNullException(nameof(rollback));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CreateShim()
|
||||||
|
{
|
||||||
|
_createShim(_locationOfShimDuringTransaction);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Commit(Enlistment enlistment)
|
||||||
|
{
|
||||||
|
enlistment.Done();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void InDoubt(Enlistment enlistment)
|
||||||
|
{
|
||||||
|
Rollback(enlistment);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Prepare(PreparingEnlistment preparingEnlistment)
|
||||||
|
{
|
||||||
|
preparingEnlistment.Done();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Rollback(Enlistment enlistment)
|
||||||
|
{
|
||||||
|
_rollback(_locationOfShimDuringTransaction);
|
||||||
|
|
||||||
|
enlistment.Done();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,10 +2,12 @@
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Transactions;
|
||||||
using System.Xml.Linq;
|
using System.Xml.Linq;
|
||||||
using Microsoft.DotNet.Cli.Utils;
|
using Microsoft.DotNet.Cli.Utils;
|
||||||
using Microsoft.DotNet.Tools;
|
using Microsoft.DotNet.Tools;
|
||||||
|
@ -22,11 +24,38 @@ namespace Microsoft.DotNet.ShellShim
|
||||||
|
|
||||||
public ShellShimMaker(string pathToPlaceShim)
|
public ShellShimMaker(string pathToPlaceShim)
|
||||||
{
|
{
|
||||||
_pathToPlaceShim =
|
_pathToPlaceShim = pathToPlaceShim ?? throw new ArgumentNullException(nameof(pathToPlaceShim));
|
||||||
pathToPlaceShim ?? throw new ArgumentNullException(nameof(pathToPlaceShim));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void CreateShim(FilePath packageExecutable, string shellCommandName)
|
public void CreateShim(FilePath packageExecutable, string shellCommandName)
|
||||||
|
{
|
||||||
|
var createShimTransaction = new CreateShimTransaction(
|
||||||
|
createShim: locationOfShimDuringTransaction =>
|
||||||
|
{
|
||||||
|
EnsureCommandNameUniqueness(shellCommandName);
|
||||||
|
PlaceShim(packageExecutable, shellCommandName, locationOfShimDuringTransaction);
|
||||||
|
},
|
||||||
|
rollback: locationOfShimDuringTransaction =>
|
||||||
|
{
|
||||||
|
foreach (FilePath f in locationOfShimDuringTransaction)
|
||||||
|
{
|
||||||
|
if (File.Exists(f.Value))
|
||||||
|
{
|
||||||
|
File.Delete(f.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
using (var transactionScope = new TransactionScope())
|
||||||
|
{
|
||||||
|
Transaction.Current.EnlistVolatile(createShimTransaction, EnlistmentOptions.None);
|
||||||
|
createShimTransaction.CreateShim();
|
||||||
|
|
||||||
|
transactionScope.Complete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PlaceShim(FilePath packageExecutable, string shellCommandName, List<FilePath> locationOfShimDuringTransaction)
|
||||||
{
|
{
|
||||||
FilePath shimPath = GetShimPath(shellCommandName);
|
FilePath shimPath = GetShimPath(shellCommandName);
|
||||||
|
|
||||||
|
@ -37,12 +66,20 @@ namespace Microsoft.DotNet.ShellShim
|
||||||
|
|
||||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||||
{
|
{
|
||||||
CreateConfigFile(shimPath.Value + ".config", entryPoint: packageExecutable, runner: "dotnet");
|
FilePath windowsConfig = GetWindowsConfigPath(shellCommandName);
|
||||||
|
CreateConfigFile(
|
||||||
|
windowsConfig,
|
||||||
|
entryPoint: packageExecutable,
|
||||||
|
runner: "dotnet");
|
||||||
|
|
||||||
|
locationOfShimDuringTransaction.Add(windowsConfig);
|
||||||
|
|
||||||
using (var shim = File.Create(shimPath.Value))
|
using (var shim = File.Create(shimPath.Value))
|
||||||
using (var exe = typeof(ShellShimMaker).Assembly.GetManifestResourceStream(LauncherExeResourceName))
|
using (var exe = typeof(ShellShimMaker).Assembly.GetManifestResourceStream(LauncherExeResourceName))
|
||||||
{
|
{
|
||||||
exe.CopyTo(shim);
|
exe.CopyTo(shim);
|
||||||
}
|
}
|
||||||
|
locationOfShimDuringTransaction.Add(shimPath);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -51,6 +88,7 @@ namespace Microsoft.DotNet.ShellShim
|
||||||
script.AppendLine($"dotnet {packageExecutable.ToQuotedString()} \"$@\"");
|
script.AppendLine($"dotnet {packageExecutable.ToQuotedString()} \"$@\"");
|
||||||
|
|
||||||
File.WriteAllText(shimPath.Value, script.ToString());
|
File.WriteAllText(shimPath.Value, script.ToString());
|
||||||
|
locationOfShimDuringTransaction.Add(shimPath);
|
||||||
|
|
||||||
SetUserExecutionPermissionToShimFile(shimPath);
|
SetUserExecutionPermissionToShimFile(shimPath);
|
||||||
}
|
}
|
||||||
|
@ -58,7 +96,7 @@ namespace Microsoft.DotNet.ShellShim
|
||||||
|
|
||||||
public void EnsureCommandNameUniqueness(string shellCommandName)
|
public void EnsureCommandNameUniqueness(string shellCommandName)
|
||||||
{
|
{
|
||||||
if (File.Exists(Path.Combine(_pathToPlaceShim, shellCommandName)))
|
if (File.Exists(GetShimPath(shellCommandName).Value))
|
||||||
{
|
{
|
||||||
throw new GracefulException(
|
throw new GracefulException(
|
||||||
string.Format(CommonLocalizableStrings.FailInstallToolSameName,
|
string.Format(CommonLocalizableStrings.FailInstallToolSameName,
|
||||||
|
@ -66,7 +104,7 @@ namespace Microsoft.DotNet.ShellShim
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void CreateConfigFile(string outputPath, FilePath entryPoint, string runner)
|
internal void CreateConfigFile(FilePath outputPath, FilePath entryPoint, string runner)
|
||||||
{
|
{
|
||||||
XDocument config;
|
XDocument config;
|
||||||
using(var resource = typeof(ShellShimMaker).Assembly.GetManifestResourceStream(LauncherConfigResourceName))
|
using(var resource = typeof(ShellShimMaker).Assembly.GetManifestResourceStream(LauncherConfigResourceName))
|
||||||
|
@ -77,11 +115,16 @@ namespace Microsoft.DotNet.ShellShim
|
||||||
var appSettings = config.Descendants("appSettings").First();
|
var appSettings = config.Descendants("appSettings").First();
|
||||||
appSettings.Add(new XElement("add", new XAttribute("key", "entryPoint"), new XAttribute("value", entryPoint.Value)));
|
appSettings.Add(new XElement("add", new XAttribute("key", "entryPoint"), new XAttribute("value", entryPoint.Value)));
|
||||||
appSettings.Add(new XElement("add", new XAttribute("key", "runner"), new XAttribute("value", runner ?? string.Empty)));
|
appSettings.Add(new XElement("add", new XAttribute("key", "runner"), new XAttribute("value", runner ?? string.Empty)));
|
||||||
config.Save(outputPath);
|
config.Save(outputPath.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Remove(string shellCommandName)
|
public void Remove(string shellCommandName)
|
||||||
{
|
{
|
||||||
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||||
|
{
|
||||||
|
File.Delete(GetWindowsConfigPath(shellCommandName).Value);
|
||||||
|
}
|
||||||
|
|
||||||
File.Delete(GetShimPath(shellCommandName).Value);
|
File.Delete(GetShimPath(shellCommandName).Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -96,6 +139,11 @@ namespace Microsoft.DotNet.ShellShim
|
||||||
return new FilePath(scriptPath);
|
return new FilePath(scriptPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private FilePath GetWindowsConfigPath(string shellCommandName)
|
||||||
|
{
|
||||||
|
return new FilePath(GetShimPath(shellCommandName).Value + ".config");
|
||||||
|
}
|
||||||
|
|
||||||
private static void SetUserExecutionPermissionToShimFile(FilePath scriptPath)
|
private static void SetUserExecutionPermissionToShimFile(FilePath scriptPath)
|
||||||
{
|
{
|
||||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||||
|
|
52
src/dotnet/ToolPackage/ToolPackageObtainTransaction.cs
Normal file
52
src/dotnet/ToolPackage/ToolPackageObtainTransaction.cs
Normal 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;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Transactions;
|
||||||
|
using Microsoft.Extensions.EnvironmentAbstractions;
|
||||||
|
|
||||||
|
namespace Microsoft.DotNet.ToolPackage
|
||||||
|
{
|
||||||
|
internal class ToolPackageObtainTransaction : IEnlistmentNotification
|
||||||
|
{
|
||||||
|
private readonly Func<List<DirectoryPath>, ToolConfigurationAndExecutablePath> _obtainAndReturnExecutablePath;
|
||||||
|
private readonly Action<List<DirectoryPath>> _rollback;
|
||||||
|
private List<DirectoryPath> _locationOfPackageDuringTransaction = new List<DirectoryPath>();
|
||||||
|
|
||||||
|
public ToolPackageObtainTransaction(
|
||||||
|
Func<List<DirectoryPath>, ToolConfigurationAndExecutablePath> obtainAndReturnExecutablePath,
|
||||||
|
Action<List<DirectoryPath>> rollback)
|
||||||
|
{
|
||||||
|
_obtainAndReturnExecutablePath = obtainAndReturnExecutablePath ?? throw new ArgumentNullException(nameof(obtainAndReturnExecutablePath));
|
||||||
|
_rollback = rollback ?? throw new ArgumentNullException(nameof(rollback));
|
||||||
|
}
|
||||||
|
|
||||||
|
public ToolConfigurationAndExecutablePath ObtainAndReturnExecutablePath()
|
||||||
|
{
|
||||||
|
return _obtainAndReturnExecutablePath(_locationOfPackageDuringTransaction);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Commit(Enlistment enlistment)
|
||||||
|
{
|
||||||
|
enlistment.Done();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void InDoubt(Enlistment enlistment)
|
||||||
|
{
|
||||||
|
Rollback(enlistment);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Prepare(PreparingEnlistment preparingEnlistment)
|
||||||
|
{
|
||||||
|
preparingEnlistment.Done();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Rollback(Enlistment enlistment)
|
||||||
|
{
|
||||||
|
_rollback(_locationOfPackageDuringTransaction);
|
||||||
|
|
||||||
|
enlistment.Done();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,12 +1,11 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Transactions;
|
||||||
using System.Xml.Linq;
|
using System.Xml.Linq;
|
||||||
using Microsoft.DotNet.Tools;
|
using Microsoft.DotNet.Cli;
|
||||||
using Microsoft.DotNet.Cli.Utils;
|
using Microsoft.DotNet.Cli.Utils;
|
||||||
using Microsoft.DotNet.Configurer;
|
using Microsoft.DotNet.Tools;
|
||||||
using Microsoft.Extensions.EnvironmentAbstractions;
|
using Microsoft.Extensions.EnvironmentAbstractions;
|
||||||
using NuGet.ProjectModel;
|
using NuGet.ProjectModel;
|
||||||
|
|
||||||
|
@ -42,6 +41,69 @@ namespace Microsoft.DotNet.ToolPackage
|
||||||
string targetframework = null,
|
string targetframework = null,
|
||||||
string source = null,
|
string source = null,
|
||||||
string verbosity = null)
|
string verbosity = null)
|
||||||
|
{
|
||||||
|
var stageDirectory = _toolsPath.WithSubDirectories(".stage", Path.GetRandomFileName());
|
||||||
|
|
||||||
|
var toolPackageObtainTransaction = new ToolPackageObtainTransaction(
|
||||||
|
obtainAndReturnExecutablePath: (locationOfPackageDuringTransaction) =>
|
||||||
|
{
|
||||||
|
if (Directory.Exists(_toolsPath.WithSubDirectories(packageId).Value))
|
||||||
|
{
|
||||||
|
throw new PackageObtainException(
|
||||||
|
string.Format(CommonLocalizableStrings.ToolPackageConflictPackageId, packageId));
|
||||||
|
}
|
||||||
|
|
||||||
|
locationOfPackageDuringTransaction.Add(stageDirectory);
|
||||||
|
var toolConfigurationAndExecutablePath = ObtainAndReturnExecutablePathInStageFolder(
|
||||||
|
packageId,
|
||||||
|
stageDirectory,
|
||||||
|
packageVersion,
|
||||||
|
nugetconfig,
|
||||||
|
targetframework,
|
||||||
|
source,
|
||||||
|
verbosity);
|
||||||
|
|
||||||
|
DirectoryPath destinationDirectory = _toolsPath.WithSubDirectories(packageId);
|
||||||
|
|
||||||
|
Directory.Move(
|
||||||
|
stageDirectory.Value,
|
||||||
|
destinationDirectory.Value);
|
||||||
|
|
||||||
|
locationOfPackageDuringTransaction.Clear();
|
||||||
|
locationOfPackageDuringTransaction.Add(destinationDirectory);
|
||||||
|
|
||||||
|
return toolConfigurationAndExecutablePath;
|
||||||
|
},
|
||||||
|
rollback: (locationOfPackageDuringTransaction) =>
|
||||||
|
{
|
||||||
|
foreach (DirectoryPath l in locationOfPackageDuringTransaction)
|
||||||
|
{
|
||||||
|
if (Directory.Exists(l.Value))
|
||||||
|
{
|
||||||
|
Directory.Delete(l.Value, recursive: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
using (var transactionScope = new TransactionScope())
|
||||||
|
{
|
||||||
|
Transaction.Current.EnlistVolatile(toolPackageObtainTransaction, EnlistmentOptions.None);
|
||||||
|
var toolConfigurationAndExecutablePath = toolPackageObtainTransaction.ObtainAndReturnExecutablePath();
|
||||||
|
|
||||||
|
transactionScope.Complete();
|
||||||
|
return toolConfigurationAndExecutablePath;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ToolConfigurationAndExecutablePath ObtainAndReturnExecutablePathInStageFolder(
|
||||||
|
string packageId,
|
||||||
|
DirectoryPath stageDirectory,
|
||||||
|
string packageVersion = null,
|
||||||
|
FilePath? nugetconfig = null,
|
||||||
|
string targetframework = null,
|
||||||
|
string source = null,
|
||||||
|
string verbosity = null)
|
||||||
{
|
{
|
||||||
if (packageId == null)
|
if (packageId == null)
|
||||||
{
|
{
|
||||||
|
@ -65,34 +127,34 @@ namespace Microsoft.DotNet.ToolPackage
|
||||||
|
|
||||||
var packageVersionOrPlaceHolder = new PackageVersion(packageVersion);
|
var packageVersionOrPlaceHolder = new PackageVersion(packageVersion);
|
||||||
|
|
||||||
DirectoryPath toolDirectory =
|
DirectoryPath nugetSandboxDirectory =
|
||||||
CreateIndividualToolVersionDirectory(packageId, packageVersionOrPlaceHolder);
|
CreateNugetSandboxDirectory(packageVersionOrPlaceHolder, stageDirectory);
|
||||||
|
|
||||||
FilePath tempProjectPath = CreateTempProject(
|
FilePath tempProjectPath = CreateTempProject(
|
||||||
packageId,
|
packageId,
|
||||||
packageVersionOrPlaceHolder,
|
packageVersionOrPlaceHolder,
|
||||||
targetframework,
|
targetframework,
|
||||||
toolDirectory);
|
nugetSandboxDirectory);
|
||||||
|
|
||||||
_projectRestorer.Restore(tempProjectPath, toolDirectory, nugetconfig, source, verbosity);
|
_projectRestorer.Restore(tempProjectPath, nugetSandboxDirectory, nugetconfig, source, verbosity);
|
||||||
|
|
||||||
if (packageVersionOrPlaceHolder.IsPlaceholder)
|
if (packageVersionOrPlaceHolder.IsPlaceholder)
|
||||||
{
|
{
|
||||||
var concreteVersion =
|
var concreteVersion =
|
||||||
new DirectoryInfo(
|
new DirectoryInfo(
|
||||||
Directory.GetDirectories(
|
Directory.GetDirectories(
|
||||||
toolDirectory.WithSubDirectories(packageId).Value).Single()).Name;
|
nugetSandboxDirectory.WithSubDirectories(packageId).Value).Single()).Name;
|
||||||
DirectoryPath versioned =
|
DirectoryPath versioned =
|
||||||
toolDirectory.GetParentPath().WithSubDirectories(concreteVersion);
|
nugetSandboxDirectory.GetParentPath().WithSubDirectories(concreteVersion);
|
||||||
|
|
||||||
MoveToVersionedDirectory(versioned, toolDirectory);
|
MoveToVersionedDirectory(versioned, nugetSandboxDirectory);
|
||||||
|
|
||||||
toolDirectory = versioned;
|
nugetSandboxDirectory = versioned;
|
||||||
packageVersion = concreteVersion;
|
packageVersion = concreteVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
LockFile lockFile = new LockFileFormat()
|
LockFile lockFile = new LockFileFormat()
|
||||||
.ReadWithLock(toolDirectory.WithFile("project.assets.json").Value)
|
.ReadWithLock(nugetSandboxDirectory.WithFile("project.assets.json").Value)
|
||||||
.Result;
|
.Result;
|
||||||
|
|
||||||
LockFileItem dotnetToolSettings = FindAssetInLockFile(lockFile, "DotnetToolSettings.xml", packageId);
|
LockFileItem dotnetToolSettings = FindAssetInLockFile(lockFile, "DotnetToolSettings.xml", packageId);
|
||||||
|
@ -104,7 +166,7 @@ namespace Microsoft.DotNet.ToolPackage
|
||||||
}
|
}
|
||||||
|
|
||||||
FilePath toolConfigurationPath =
|
FilePath toolConfigurationPath =
|
||||||
toolDirectory
|
nugetSandboxDirectory
|
||||||
.WithSubDirectories(packageId, packageVersion)
|
.WithSubDirectories(packageId, packageVersion)
|
||||||
.WithFile(dotnetToolSettings.Path);
|
.WithFile(dotnetToolSettings.Path);
|
||||||
|
|
||||||
|
@ -122,7 +184,9 @@ namespace Microsoft.DotNet.ToolPackage
|
||||||
|
|
||||||
return new ToolConfigurationAndExecutablePath(
|
return new ToolConfigurationAndExecutablePath(
|
||||||
toolConfiguration,
|
toolConfiguration,
|
||||||
toolDirectory.WithSubDirectories(
|
_toolsPath.WithSubDirectories(
|
||||||
|
packageId,
|
||||||
|
packageVersion,
|
||||||
packageId,
|
packageId,
|
||||||
packageVersion)
|
packageVersion)
|
||||||
.WithFile(entryPointFromLockFile.Path));
|
.WithFile(entryPointFromLockFile.Path));
|
||||||
|
@ -186,18 +250,19 @@ namespace Microsoft.DotNet.ToolPackage
|
||||||
))
|
))
|
||||||
));
|
));
|
||||||
|
|
||||||
|
|
||||||
File.WriteAllText(tempProjectPath.Value,
|
File.WriteAllText(tempProjectPath.Value,
|
||||||
tempProjectContent.ToString());
|
tempProjectContent.ToString());
|
||||||
|
|
||||||
return tempProjectPath;
|
return tempProjectPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
private DirectoryPath CreateIndividualToolVersionDirectory(
|
private DirectoryPath CreateNugetSandboxDirectory(
|
||||||
string packageId,
|
PackageVersion packageVersion,
|
||||||
PackageVersion packageVersion)
|
DirectoryPath stageDirectory
|
||||||
|
)
|
||||||
{
|
{
|
||||||
DirectoryPath individualTool = _toolsPath.WithSubDirectories(packageId);
|
DirectoryPath individualToolVersion = stageDirectory.WithSubDirectories(packageVersion.Value);
|
||||||
DirectoryPath individualToolVersion = individualTool.WithSubDirectories(packageVersion.Value);
|
|
||||||
EnsureDirectoryExists(individualToolVersion);
|
EnsureDirectoryExists(individualToolVersion);
|
||||||
return individualToolVersion;
|
return individualToolVersion;
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Transactions;
|
||||||
using Microsoft.DotNet.Cli;
|
using Microsoft.DotNet.Cli;
|
||||||
using Microsoft.DotNet.Cli.CommandLine;
|
using Microsoft.DotNet.Cli.CommandLine;
|
||||||
using Microsoft.DotNet.Cli.Utils;
|
using Microsoft.DotNet.Cli.Utils;
|
||||||
|
@ -21,6 +22,7 @@ namespace Microsoft.DotNet.Tools.Install.Tool
|
||||||
private readonly IEnvironmentPathInstruction _environmentPathInstruction;
|
private readonly IEnvironmentPathInstruction _environmentPathInstruction;
|
||||||
private readonly IShellShimMaker _shellShimMaker;
|
private readonly IShellShimMaker _shellShimMaker;
|
||||||
private readonly IReporter _reporter;
|
private readonly IReporter _reporter;
|
||||||
|
private readonly IReporter _errorReporter;
|
||||||
|
|
||||||
private readonly string _packageId;
|
private readonly string _packageId;
|
||||||
private readonly string _packageVersion;
|
private readonly string _packageVersion;
|
||||||
|
@ -69,13 +71,12 @@ namespace Microsoft.DotNet.Tools.Install.Tool
|
||||||
|
|
||||||
_shellShimMaker = shellShimMaker ?? new ShellShimMaker(cliFolderPathCalculator.ToolsShimPath);
|
_shellShimMaker = shellShimMaker ?? new ShellShimMaker(cliFolderPathCalculator.ToolsShimPath);
|
||||||
|
|
||||||
_reporter = reporter;
|
_reporter = (reporter ?? Reporter.Output);
|
||||||
|
_errorReporter = (reporter ?? Reporter.Error);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override int Execute()
|
public override int Execute()
|
||||||
{
|
{
|
||||||
var reporter = (_reporter ?? Reporter.Output);
|
|
||||||
var errorReporter = (_reporter ?? Reporter.Error);
|
|
||||||
if (!_global)
|
if (!_global)
|
||||||
{
|
{
|
||||||
throw new GracefulException(LocalizableStrings.InstallToolCommandOnlySupportGlobal);
|
throw new GracefulException(LocalizableStrings.InstallToolCommandOnlySupportGlobal);
|
||||||
|
@ -83,10 +84,23 @@ namespace Microsoft.DotNet.Tools.Install.Tool
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var toolConfigurationAndExecutablePath = ObtainPackage();
|
FilePath? configFile = null;
|
||||||
|
if (_configFilePath != null)
|
||||||
|
{
|
||||||
|
configFile = new FilePath(_configFilePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
using (var transactionScope = new TransactionScope())
|
||||||
|
{
|
||||||
|
var toolConfigurationAndExecutablePath = _toolPackageObtainer.ObtainAndReturnExecutablePath(
|
||||||
|
packageId: _packageId,
|
||||||
|
packageVersion: _packageVersion,
|
||||||
|
nugetconfig: configFile,
|
||||||
|
targetframework: _framework,
|
||||||
|
source: _source,
|
||||||
|
verbosity: _verbosity);
|
||||||
|
|
||||||
var commandName = toolConfigurationAndExecutablePath.Configuration.CommandName;
|
var commandName = toolConfigurationAndExecutablePath.Configuration.CommandName;
|
||||||
_shellShimMaker.EnsureCommandNameUniqueness(commandName);
|
|
||||||
|
|
||||||
_shellShimMaker.CreateShim(
|
_shellShimMaker.CreateShim(
|
||||||
toolConfigurationAndExecutablePath.Executable,
|
toolConfigurationAndExecutablePath.Executable,
|
||||||
|
@ -95,42 +109,28 @@ namespace Microsoft.DotNet.Tools.Install.Tool
|
||||||
_environmentPathInstruction
|
_environmentPathInstruction
|
||||||
.PrintAddPathInstructionIfPathDoesNotExist();
|
.PrintAddPathInstructionIfPathDoesNotExist();
|
||||||
|
|
||||||
reporter.WriteLine(
|
_reporter.WriteLine(
|
||||||
string.Format(LocalizableStrings.InstallationSucceeded, commandName));
|
string.Format(LocalizableStrings.InstallationSucceeded, commandName));
|
||||||
|
transactionScope.Complete();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (PackageObtainException ex)
|
catch (PackageObtainException ex)
|
||||||
{
|
{
|
||||||
errorReporter.WriteLine(ex.Message.Red());
|
_errorReporter.WriteLine(ex.Message.Red());
|
||||||
errorReporter.WriteLine(string.Format(LocalizableStrings.ToolInstallationFailed, _packageId).Red());
|
_errorReporter.WriteLine(string.Format(LocalizableStrings.ToolInstallationFailed, _packageId).Red());
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
catch (ToolConfigurationException ex)
|
catch (ToolConfigurationException ex)
|
||||||
{
|
{
|
||||||
errorReporter.WriteLine(
|
_errorReporter.WriteLine(
|
||||||
string.Format(
|
string.Format(
|
||||||
LocalizableStrings.InvalidToolConfiguration,
|
LocalizableStrings.InvalidToolConfiguration,
|
||||||
ex.Message).Red());
|
ex.Message).Red());
|
||||||
errorReporter.WriteLine(string.Format(LocalizableStrings.ToolInstallationFailedContactAuthor, _packageId).Red());
|
_errorReporter.WriteLine(string.Format(LocalizableStrings.ToolInstallationFailedContactAuthor, _packageId).Red());
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ToolConfigurationAndExecutablePath ObtainPackage()
|
|
||||||
{
|
|
||||||
FilePath? configFile = null;
|
|
||||||
if (_configFilePath != null)
|
|
||||||
{
|
|
||||||
configFile = new FilePath(_configFilePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
return _toolPackageObtainer.ObtainAndReturnExecutablePath(
|
|
||||||
packageId: _packageId,
|
|
||||||
packageVersion: _packageVersion,
|
|
||||||
nugetconfig: configFile,
|
|
||||||
targetframework: _framework,
|
|
||||||
source: _source,
|
|
||||||
verbosity: _verbosity);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -793,6 +793,11 @@ setx PATH "%PATH%;{1}"</target>
|
||||||
<target state="new">Command '{0}' uses unsupported runner '{1}'."</target>
|
<target state="new">Command '{0}' uses unsupported runner '{1}'."</target>
|
||||||
<note />
|
<note />
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
<trans-unit id="ToolPackageConflictPackageId">
|
||||||
|
<source>Tool '{0}' is already installed.</source>
|
||||||
|
<target state="new">Tool '{0}' is already installed.</target>
|
||||||
|
<note />
|
||||||
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
</xliff>
|
</xliff>
|
|
@ -793,6 +793,11 @@ setx PATH "%PATH%;{1}"</target>
|
||||||
<target state="new">Command '{0}' uses unsupported runner '{1}'."</target>
|
<target state="new">Command '{0}' uses unsupported runner '{1}'."</target>
|
||||||
<note />
|
<note />
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
<trans-unit id="ToolPackageConflictPackageId">
|
||||||
|
<source>Tool '{0}' is already installed.</source>
|
||||||
|
<target state="new">Tool '{0}' is already installed.</target>
|
||||||
|
<note />
|
||||||
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
</xliff>
|
</xliff>
|
|
@ -793,6 +793,11 @@ setx PATH "%PATH%;{1}"</target>
|
||||||
<target state="new">Command '{0}' uses unsupported runner '{1}'."</target>
|
<target state="new">Command '{0}' uses unsupported runner '{1}'."</target>
|
||||||
<note />
|
<note />
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
<trans-unit id="ToolPackageConflictPackageId">
|
||||||
|
<source>Tool '{0}' is already installed.</source>
|
||||||
|
<target state="new">Tool '{0}' is already installed.</target>
|
||||||
|
<note />
|
||||||
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
</xliff>
|
</xliff>
|
|
@ -793,6 +793,11 @@ setx PATH "%PATH%;{1}"</target>
|
||||||
<target state="new">Command '{0}' uses unsupported runner '{1}'."</target>
|
<target state="new">Command '{0}' uses unsupported runner '{1}'."</target>
|
||||||
<note />
|
<note />
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
<trans-unit id="ToolPackageConflictPackageId">
|
||||||
|
<source>Tool '{0}' is already installed.</source>
|
||||||
|
<target state="new">Tool '{0}' is already installed.</target>
|
||||||
|
<note />
|
||||||
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
</xliff>
|
</xliff>
|
|
@ -793,6 +793,11 @@ setx PATH "%PATH%;{1}"</target>
|
||||||
<target state="new">Command '{0}' uses unsupported runner '{1}'."</target>
|
<target state="new">Command '{0}' uses unsupported runner '{1}'."</target>
|
||||||
<note />
|
<note />
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
<trans-unit id="ToolPackageConflictPackageId">
|
||||||
|
<source>Tool '{0}' is already installed.</source>
|
||||||
|
<target state="new">Tool '{0}' is already installed.</target>
|
||||||
|
<note />
|
||||||
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
</xliff>
|
</xliff>
|
|
@ -793,6 +793,11 @@ setx PATH "%PATH%;{1}"</target>
|
||||||
<target state="new">Command '{0}' uses unsupported runner '{1}'."</target>
|
<target state="new">Command '{0}' uses unsupported runner '{1}'."</target>
|
||||||
<note />
|
<note />
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
<trans-unit id="ToolPackageConflictPackageId">
|
||||||
|
<source>Tool '{0}' is already installed.</source>
|
||||||
|
<target state="new">Tool '{0}' is already installed.</target>
|
||||||
|
<note />
|
||||||
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
</xliff>
|
</xliff>
|
|
@ -793,6 +793,11 @@ setx PATH "%PATH%;{1}"</target>
|
||||||
<target state="new">Command '{0}' uses unsupported runner '{1}'."</target>
|
<target state="new">Command '{0}' uses unsupported runner '{1}'."</target>
|
||||||
<note />
|
<note />
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
<trans-unit id="ToolPackageConflictPackageId">
|
||||||
|
<source>Tool '{0}' is already installed.</source>
|
||||||
|
<target state="new">Tool '{0}' is already installed.</target>
|
||||||
|
<note />
|
||||||
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
</xliff>
|
</xliff>
|
|
@ -793,6 +793,11 @@ setx PATH "%PATH%;{1}"</target>
|
||||||
<target state="new">Command '{0}' uses unsupported runner '{1}'."</target>
|
<target state="new">Command '{0}' uses unsupported runner '{1}'."</target>
|
||||||
<note />
|
<note />
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
<trans-unit id="ToolPackageConflictPackageId">
|
||||||
|
<source>Tool '{0}' is already installed.</source>
|
||||||
|
<target state="new">Tool '{0}' is already installed.</target>
|
||||||
|
<note />
|
||||||
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
</xliff>
|
</xliff>
|
|
@ -793,6 +793,11 @@ setx PATH "%PATH%;{1}"</target>
|
||||||
<target state="new">Command '{0}' uses unsupported runner '{1}'."</target>
|
<target state="new">Command '{0}' uses unsupported runner '{1}'."</target>
|
||||||
<note />
|
<note />
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
<trans-unit id="ToolPackageConflictPackageId">
|
||||||
|
<source>Tool '{0}' is already installed.</source>
|
||||||
|
<target state="new">Tool '{0}' is already installed.</target>
|
||||||
|
<note />
|
||||||
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
</xliff>
|
</xliff>
|
|
@ -793,6 +793,11 @@ setx PATH "%PATH%;{1}"</target>
|
||||||
<target state="new">Command '{0}' uses unsupported runner '{1}'."</target>
|
<target state="new">Command '{0}' uses unsupported runner '{1}'."</target>
|
||||||
<note />
|
<note />
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
<trans-unit id="ToolPackageConflictPackageId">
|
||||||
|
<source>Tool '{0}' is already installed.</source>
|
||||||
|
<target state="new">Tool '{0}' is already installed.</target>
|
||||||
|
<note />
|
||||||
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
</xliff>
|
</xliff>
|
|
@ -793,6 +793,11 @@ setx PATH "%PATH%;{1}"</target>
|
||||||
<target state="new">Command '{0}' uses unsupported runner '{1}'."</target>
|
<target state="new">Command '{0}' uses unsupported runner '{1}'."</target>
|
||||||
<note />
|
<note />
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
<trans-unit id="ToolPackageConflictPackageId">
|
||||||
|
<source>Tool '{0}' is already installed.</source>
|
||||||
|
<target state="new">Tool '{0}' is already installed.</target>
|
||||||
|
<note />
|
||||||
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
</xliff>
|
</xliff>
|
|
@ -793,6 +793,11 @@ setx PATH "%PATH%;{1}"</target>
|
||||||
<target state="new">Command '{0}' uses unsupported runner '{1}'."</target>
|
<target state="new">Command '{0}' uses unsupported runner '{1}'."</target>
|
||||||
<note />
|
<note />
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
<trans-unit id="ToolPackageConflictPackageId">
|
||||||
|
<source>Tool '{0}' is already installed.</source>
|
||||||
|
<target state="new">Tool '{0}' is already installed.</target>
|
||||||
|
<note />
|
||||||
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
</xliff>
|
</xliff>
|
|
@ -793,6 +793,11 @@ setx PATH "%PATH%;{1}"</target>
|
||||||
<target state="new">Command '{0}' uses unsupported runner '{1}'."</target>
|
<target state="new">Command '{0}' uses unsupported runner '{1}'."</target>
|
||||||
<note />
|
<note />
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
<trans-unit id="ToolPackageConflictPackageId">
|
||||||
|
<source>Tool '{0}' is already installed.</source>
|
||||||
|
<target state="new">Tool '{0}' is already installed.</target>
|
||||||
|
<note />
|
||||||
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
</xliff>
|
</xliff>
|
|
@ -170,6 +170,11 @@ namespace Microsoft.DotNet.Configurer.UnitTests
|
||||||
_directories.Add(path);
|
_directories.Add(path);
|
||||||
CreateDirectoryInvoked = true;
|
CreateDirectoryInvoked = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Delete(string path, bool recursive)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -124,6 +124,11 @@ namespace Microsoft.DotNet.Configurer.UnitTests
|
||||||
{
|
{
|
||||||
throw new UnauthorizedAccessException();
|
throw new UnauthorizedAccessException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Delete(string path)
|
||||||
|
{
|
||||||
|
throw new UnauthorizedAccessException();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class NoPermissionDirectoryFake : IDirectory
|
private class NoPermissionDirectoryFake : IDirectory
|
||||||
|
@ -153,6 +158,11 @@ namespace Microsoft.DotNet.Configurer.UnitTests
|
||||||
{
|
{
|
||||||
throw new UnauthorizedAccessException();
|
throw new UnauthorizedAccessException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Delete(string path, bool recursive)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class Counter
|
private class Counter
|
||||||
|
|
|
@ -197,6 +197,11 @@ namespace Microsoft.DotNet.Configurer.UnitTests
|
||||||
{
|
{
|
||||||
throw new UnauthorizedAccessException();
|
throw new UnauthorizedAccessException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Delete(string path, bool recursive)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class FileMock : IFile
|
private class FileMock : IFile
|
||||||
|
@ -256,6 +261,11 @@ namespace Microsoft.DotNet.Configurer.UnitTests
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Delete(string path)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class MockStream : MemoryStream
|
private class MockStream : MemoryStream
|
||||||
|
|
|
@ -50,6 +50,11 @@ namespace Microsoft.DotNet.ShellShim.Tests
|
||||||
_files[path] = content;
|
_files[path] = content;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Delete(string path)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
public static FakeFile Empty => new FakeFile(new Dictionary<string, string>());
|
public static FakeFile Empty => new FakeFile(new Dictionary<string, string>());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,12 +6,13 @@ using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Transactions;
|
||||||
using System.Xml.Linq;
|
using System.Xml.Linq;
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using Microsoft.DotNet.Cli.Utils;
|
using Microsoft.DotNet.Cli.Utils;
|
||||||
using Microsoft.DotNet.TestFramework;
|
using Microsoft.DotNet.TestFramework;
|
||||||
|
using Microsoft.DotNet.ToolPackage;
|
||||||
using Microsoft.DotNet.Tools.Test.Utilities;
|
using Microsoft.DotNet.Tools.Test.Utilities;
|
||||||
using Microsoft.DotNet.Tools.Test.Utilities.Mock;
|
|
||||||
using Microsoft.DotNet.Tools.Tests.ComponentMocks;
|
using Microsoft.DotNet.Tools.Tests.ComponentMocks;
|
||||||
using Microsoft.Extensions.EnvironmentAbstractions;
|
using Microsoft.Extensions.EnvironmentAbstractions;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
@ -38,15 +39,16 @@ namespace Microsoft.DotNet.ShellShim.Tests
|
||||||
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var shellShimMaker = new ShellShimMaker(TempRoot.Root);
|
var cleanFolderUnderTempRoot = GetNewCleanFolderUnderTempRoot();
|
||||||
|
var shellShimMaker = new ShellShimMaker(cleanFolderUnderTempRoot);
|
||||||
|
|
||||||
var tmpFile = Path.Combine(TempRoot.Root, Path.GetRandomFileName());
|
var tmpFile = new FilePath(Path.Combine(cleanFolderUnderTempRoot, Path.GetRandomFileName()));
|
||||||
|
|
||||||
shellShimMaker.CreateConfigFile(tmpFile, entryPoint, runner);
|
shellShimMaker.CreateConfigFile(tmpFile, entryPoint, runner);
|
||||||
|
|
||||||
new FileInfo(tmpFile).Should().Exist();
|
new FileInfo(tmpFile.Value).Should().Exist();
|
||||||
|
|
||||||
var generated = XDocument.Load(tmpFile);
|
var generated = XDocument.Load(tmpFile.Value);
|
||||||
|
|
||||||
generated.Descendants("appSettings")
|
generated.Descendants("appSettings")
|
||||||
.Descendants("add")
|
.Descendants("add")
|
||||||
|
@ -61,13 +63,33 @@ namespace Microsoft.DotNet.ShellShim.Tests
|
||||||
{
|
{
|
||||||
var outputDll = MakeHelloWorldExecutableDll();
|
var outputDll = MakeHelloWorldExecutableDll();
|
||||||
|
|
||||||
var shellShimMaker = new ShellShimMaker(TempRoot.Root);
|
var cleanFolderUnderTempRoot = GetNewCleanFolderUnderTempRoot();
|
||||||
|
var shellShimMaker = new ShellShimMaker(cleanFolderUnderTempRoot);
|
||||||
var shellCommandName = nameof(ShellShimMakerTests) + Path.GetRandomFileName();
|
var shellCommandName = nameof(ShellShimMakerTests) + Path.GetRandomFileName();
|
||||||
|
|
||||||
shellShimMaker.CreateShim(
|
shellShimMaker.CreateShim(outputDll, shellCommandName);
|
||||||
new FilePath(outputDll.FullName),
|
|
||||||
shellCommandName);
|
var stdOut = ExecuteInShell(shellCommandName, cleanFolderUnderTempRoot);
|
||||||
var stdOut = ExecuteInShell(shellCommandName);
|
|
||||||
|
stdOut.Should().Contain("Hello World");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GivenAnExecutablePathItCanGenerateShimFileInTransaction()
|
||||||
|
{
|
||||||
|
var outputDll = MakeHelloWorldExecutableDll();
|
||||||
|
|
||||||
|
var cleanFolderUnderTempRoot = GetNewCleanFolderUnderTempRoot();
|
||||||
|
var shellShimMaker = new ShellShimMaker(cleanFolderUnderTempRoot);
|
||||||
|
var shellCommandName = nameof(ShellShimMakerTests) + Path.GetRandomFileName();
|
||||||
|
|
||||||
|
using (var transactionScope = new TransactionScope())
|
||||||
|
{
|
||||||
|
shellShimMaker.CreateShim(outputDll, shellCommandName);
|
||||||
|
transactionScope.Complete();
|
||||||
|
}
|
||||||
|
|
||||||
|
var stdOut = ExecuteInShell(shellCommandName, cleanFolderUnderTempRoot);
|
||||||
|
|
||||||
stdOut.Should().Contain("Hello World");
|
stdOut.Should().Contain("Hello World");
|
||||||
}
|
}
|
||||||
|
@ -80,9 +102,8 @@ namespace Microsoft.DotNet.ShellShim.Tests
|
||||||
var shellShimMaker = new ShellShimMaker(Path.Combine(TempRoot.Root, extraNonExistDirectory));
|
var shellShimMaker = new ShellShimMaker(Path.Combine(TempRoot.Root, extraNonExistDirectory));
|
||||||
var shellCommandName = nameof(ShellShimMakerTests) + Path.GetRandomFileName();
|
var shellCommandName = nameof(ShellShimMakerTests) + Path.GetRandomFileName();
|
||||||
|
|
||||||
Action a = () => shellShimMaker.CreateShim(
|
Action a = () => shellShimMaker.CreateShim(outputDll, shellCommandName);
|
||||||
new FilePath(outputDll.FullName),
|
|
||||||
shellCommandName);
|
|
||||||
a.ShouldNotThrow<DirectoryNotFoundException>();
|
a.ShouldNotThrow<DirectoryNotFoundException>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,14 +115,13 @@ namespace Microsoft.DotNet.ShellShim.Tests
|
||||||
{
|
{
|
||||||
var outputDll = MakeHelloWorldExecutableDll();
|
var outputDll = MakeHelloWorldExecutableDll();
|
||||||
|
|
||||||
var shellShimMaker = new ShellShimMaker(TempRoot.Root);
|
var cleanFolderUnderTempRoot = GetNewCleanFolderUnderTempRoot();
|
||||||
|
var shellShimMaker = new ShellShimMaker(cleanFolderUnderTempRoot);
|
||||||
var shellCommandName = nameof(ShellShimMakerTests) + Path.GetRandomFileName();
|
var shellCommandName = nameof(ShellShimMakerTests) + Path.GetRandomFileName();
|
||||||
|
|
||||||
shellShimMaker.CreateShim(
|
shellShimMaker.CreateShim(outputDll, shellCommandName);
|
||||||
new FilePath(outputDll.FullName),
|
|
||||||
shellCommandName);
|
|
||||||
|
|
||||||
var stdOut = ExecuteInShell(shellCommandName, arguments);
|
var stdOut = ExecuteInShell(shellCommandName, cleanFolderUnderTempRoot, arguments);
|
||||||
|
|
||||||
for (int i = 0; i < expectedPassThru.Length; i++)
|
for (int i = 0; i < expectedPassThru.Length; i++)
|
||||||
{
|
{
|
||||||
|
@ -115,17 +135,17 @@ namespace Microsoft.DotNet.ShellShim.Tests
|
||||||
public void GivenAnExecutablePathWithExistingSameNameShimItThrows(bool testMockBehaviorIsInSync)
|
public void GivenAnExecutablePathWithExistingSameNameShimItThrows(bool testMockBehaviorIsInSync)
|
||||||
{
|
{
|
||||||
var shellCommandName = nameof(ShellShimMakerTests) + Path.GetRandomFileName();
|
var shellCommandName = nameof(ShellShimMakerTests) + Path.GetRandomFileName();
|
||||||
|
var cleanFolderUnderTempRoot = GetNewCleanFolderUnderTempRoot();
|
||||||
MakeNameConflictingCommand(TempRoot.Root, shellCommandName);
|
MakeNameConflictingCommand(cleanFolderUnderTempRoot, shellCommandName);
|
||||||
|
|
||||||
IShellShimMaker shellShimMaker;
|
IShellShimMaker shellShimMaker;
|
||||||
if (testMockBehaviorIsInSync)
|
if (testMockBehaviorIsInSync)
|
||||||
{
|
{
|
||||||
shellShimMaker = new ShellShimMakerMock(TempRoot.Root);
|
shellShimMaker = new ShellShimMakerMock(cleanFolderUnderTempRoot);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
shellShimMaker = new ShellShimMaker(TempRoot.Root);
|
shellShimMaker = new ShellShimMaker(cleanFolderUnderTempRoot);
|
||||||
}
|
}
|
||||||
|
|
||||||
Action a = () => shellShimMaker.EnsureCommandNameUniqueness(shellCommandName);
|
Action a = () => shellShimMaker.EnsureCommandNameUniqueness(shellCommandName);
|
||||||
|
@ -138,18 +158,89 @@ namespace Microsoft.DotNet.ShellShim.Tests
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData(false)]
|
[InlineData(false)]
|
||||||
[InlineData(true)]
|
[InlineData(true)]
|
||||||
public void GivenAnExecutablePathWithoutExistingSameNameShimItShouldNotThrow(bool testMockBehaviorIsInSync)
|
public void GivenAnExecutablePathWithExistingSameNameShimItRollsBack(bool testMockBehaviorIsInSync)
|
||||||
{
|
{
|
||||||
var shellCommandName = nameof(ShellShimMakerTests) + Path.GetRandomFileName();
|
var shellCommandName = nameof(ShellShimMakerTests) + Path.GetRandomFileName();
|
||||||
|
|
||||||
|
var pathToShim = GetNewCleanFolderUnderTempRoot();
|
||||||
|
MakeNameConflictingCommand(pathToShim, shellCommandName);
|
||||||
|
|
||||||
IShellShimMaker shellShimMaker;
|
IShellShimMaker shellShimMaker;
|
||||||
if (testMockBehaviorIsInSync)
|
if (testMockBehaviorIsInSync)
|
||||||
{
|
{
|
||||||
shellShimMaker = new ShellShimMakerMock(TempRoot.Root);
|
shellShimMaker = new ShellShimMakerMock(pathToShim);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
shellShimMaker = new ShellShimMaker(TempRoot.Root);
|
shellShimMaker = new ShellShimMaker(pathToShim);
|
||||||
|
}
|
||||||
|
|
||||||
|
Action a = () =>
|
||||||
|
{
|
||||||
|
using (var t = new TransactionScope())
|
||||||
|
{
|
||||||
|
shellShimMaker.CreateShim(new FilePath("dummy.dll"), shellCommandName);
|
||||||
|
|
||||||
|
t.Complete();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
a.ShouldThrow<GracefulException>();
|
||||||
|
|
||||||
|
Directory.GetFiles(pathToShim).Should().HaveCount(1, "there is only intent conflicted command");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(false)]
|
||||||
|
[InlineData(true)]
|
||||||
|
public void GivenAnExecutablePathErrorHappensItRollsBack(bool testMockBehaviorIsInSync)
|
||||||
|
{
|
||||||
|
var shellCommandName = nameof(ShellShimMakerTests) + Path.GetRandomFileName();
|
||||||
|
|
||||||
|
var pathToShim = GetNewCleanFolderUnderTempRoot();
|
||||||
|
|
||||||
|
IShellShimMaker shellShimMaker;
|
||||||
|
if (testMockBehaviorIsInSync)
|
||||||
|
{
|
||||||
|
shellShimMaker = new ShellShimMakerMock(pathToShim);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
shellShimMaker = new ShellShimMaker(pathToShim);
|
||||||
|
}
|
||||||
|
|
||||||
|
Action intendedError = () => throw new PackageObtainException();
|
||||||
|
|
||||||
|
Action a = () =>
|
||||||
|
{
|
||||||
|
using (var t = new TransactionScope())
|
||||||
|
{
|
||||||
|
shellShimMaker.CreateShim(new FilePath("dummy.dll"), shellCommandName);
|
||||||
|
|
||||||
|
intendedError();
|
||||||
|
t.Complete();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
a.ShouldThrow<PackageObtainException>();
|
||||||
|
|
||||||
|
Directory.GetFiles(pathToShim).Should().BeEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(false)]
|
||||||
|
[InlineData(true)]
|
||||||
|
public void GivenAnExecutablePathWithoutExistingSameNameShimItShouldNotThrow(bool testMockBehaviorIsInSync)
|
||||||
|
{
|
||||||
|
var shellCommandName = nameof(ShellShimMakerTests) + Path.GetRandomFileName();
|
||||||
|
var cleanFolderUnderTempRoot = GetNewCleanFolderUnderTempRoot();
|
||||||
|
|
||||||
|
IShellShimMaker shellShimMaker;
|
||||||
|
if (testMockBehaviorIsInSync)
|
||||||
|
{
|
||||||
|
shellShimMaker = new ShellShimMakerMock(cleanFolderUnderTempRoot);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
shellShimMaker = new ShellShimMaker(cleanFolderUnderTempRoot);
|
||||||
}
|
}
|
||||||
|
|
||||||
Action a = () => shellShimMaker.EnsureCommandNameUniqueness(shellCommandName);
|
Action a = () => shellShimMaker.EnsureCommandNameUniqueness(shellCommandName);
|
||||||
|
@ -158,16 +249,21 @@ namespace Microsoft.DotNet.ShellShim.Tests
|
||||||
|
|
||||||
private static void MakeNameConflictingCommand(string pathToPlaceShim, string shellCommandName)
|
private static void MakeNameConflictingCommand(string pathToPlaceShim, string shellCommandName)
|
||||||
{
|
{
|
||||||
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||||
|
{
|
||||||
|
shellCommandName = shellCommandName + ".exe";
|
||||||
|
}
|
||||||
|
|
||||||
File.WriteAllText(Path.Combine(pathToPlaceShim, shellCommandName), string.Empty);
|
File.WriteAllText(Path.Combine(pathToPlaceShim, shellCommandName), string.Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
private string ExecuteInShell(string shellCommandName, string arguments = "")
|
private string ExecuteInShell(string shellCommandName, string cleanFolderUnderTempRoot, string arguments = "")
|
||||||
{
|
{
|
||||||
ProcessStartInfo processStartInfo;
|
ProcessStartInfo processStartInfo;
|
||||||
|
|
||||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||||
{
|
{
|
||||||
var file = Path.Combine(TempRoot.Root, shellCommandName + ".exe");
|
var file = Path.Combine(cleanFolderUnderTempRoot, shellCommandName + ".exe");
|
||||||
processStartInfo = new ProcessStartInfo
|
processStartInfo = new ProcessStartInfo
|
||||||
{
|
{
|
||||||
FileName = file,
|
FileName = file,
|
||||||
|
@ -186,7 +282,7 @@ namespace Microsoft.DotNet.ShellShim.Tests
|
||||||
}
|
}
|
||||||
|
|
||||||
_output.WriteLine($"Launching '{processStartInfo.FileName} {processStartInfo.Arguments}'");
|
_output.WriteLine($"Launching '{processStartInfo.FileName} {processStartInfo.Arguments}'");
|
||||||
processStartInfo.WorkingDirectory = TempRoot.Root;
|
processStartInfo.WorkingDirectory = cleanFolderUnderTempRoot;
|
||||||
processStartInfo.EnvironmentVariables["PATH"] = Path.GetDirectoryName(new Muxer().MuxerPath);
|
processStartInfo.EnvironmentVariables["PATH"] = Path.GetDirectoryName(new Muxer().MuxerPath);
|
||||||
|
|
||||||
processStartInfo.ExecuteAndCaptureOutput(out var stdOut, out var stdErr);
|
processStartInfo.ExecuteAndCaptureOutput(out var stdOut, out var stdErr);
|
||||||
|
@ -196,7 +292,7 @@ namespace Microsoft.DotNet.ShellShim.Tests
|
||||||
return stdOut ?? "";
|
return stdOut ?? "";
|
||||||
}
|
}
|
||||||
|
|
||||||
private static FileInfo MakeHelloWorldExecutableDll()
|
private static FilePath MakeHelloWorldExecutableDll()
|
||||||
{
|
{
|
||||||
const string testAppName = "TestAppSimple";
|
const string testAppName = "TestAppSimple";
|
||||||
const string emptySpaceToTestSpaceInPath = " ";
|
const string emptySpaceToTestSpaceInPath = " ";
|
||||||
|
@ -212,7 +308,15 @@ namespace Microsoft.DotNet.ShellShim.Tests
|
||||||
.GetDirectories().Single()
|
.GetDirectories().Single()
|
||||||
.GetFile($"{testAppName}.dll");
|
.GetFile($"{testAppName}.dll");
|
||||||
|
|
||||||
return outputDll;
|
return new FilePath(outputDll.FullName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetNewCleanFolderUnderTempRoot()
|
||||||
|
{
|
||||||
|
DirectoryInfo CleanFolderUnderTempRoot = new DirectoryInfo(Path.Combine(TempRoot.Root, "cleanfolder" + Path.GetRandomFileName()));
|
||||||
|
CleanFolderUnderTempRoot.Create();
|
||||||
|
|
||||||
|
return CleanFolderUnderTempRoot.FullName;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using System.Transactions;
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using Microsoft.DotNet.Tools.Test.Utilities;
|
using Microsoft.DotNet.Tools.Test.Utilities;
|
||||||
using Microsoft.Extensions.EnvironmentAbstractions;
|
using Microsoft.Extensions.EnvironmentAbstractions;
|
||||||
|
@ -58,8 +59,7 @@ namespace Microsoft.DotNet.ToolPackage.Tests
|
||||||
bundledTargetFrameworkMoniker: new Lazy<string>(),
|
bundledTargetFrameworkMoniker: new Lazy<string>(),
|
||||||
projectRestorer: new ProjectRestorer(reporter));
|
projectRestorer: new ProjectRestorer(reporter));
|
||||||
|
|
||||||
ToolConfigurationAndExecutablePath toolConfigurationAndExecutablePath =
|
ToolConfigurationAndExecutablePath toolConfigurationAndExecutablePath = packageObtainer.ObtainAndReturnExecutablePath(
|
||||||
packageObtainer.ObtainAndReturnExecutablePath(
|
|
||||||
packageId: TestPackageId,
|
packageId: TestPackageId,
|
||||||
packageVersion: TestPackageVersion,
|
packageVersion: TestPackageVersion,
|
||||||
targetframework: _testTargetframework);
|
targetframework: _testTargetframework);
|
||||||
|
@ -109,6 +109,43 @@ namespace Microsoft.DotNet.ToolPackage.Tests
|
||||||
File.Delete(executable.Value);
|
File.Delete(executable.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(false)]
|
||||||
|
[InlineData(true)]
|
||||||
|
public void GivenNugetConfigAndPackageNameAndVersionAndTargetFrameworkWhenCallItCanDownloadThePackageInTransaction(
|
||||||
|
bool testMockBehaviorIsInSync)
|
||||||
|
{
|
||||||
|
var reporter = new BufferedReporter();
|
||||||
|
FilePath nugetConfigPath = WriteNugetConfigFileToPointToTheFeed();
|
||||||
|
var toolsPath = Path.Combine(Directory.GetCurrentDirectory(), Path.GetRandomFileName());
|
||||||
|
|
||||||
|
var packageObtainer =
|
||||||
|
ConstructDefaultPackageObtainer(toolsPath, reporter, testMockBehaviorIsInSync, nugetConfigPath.Value);
|
||||||
|
|
||||||
|
ToolConfigurationAndExecutablePath toolConfigurationAndExecutablePath;
|
||||||
|
|
||||||
|
using (var transactionScope = new TransactionScope())
|
||||||
|
{
|
||||||
|
toolConfigurationAndExecutablePath
|
||||||
|
= packageObtainer.ObtainAndReturnExecutablePath(
|
||||||
|
packageId: TestPackageId,
|
||||||
|
packageVersion: TestPackageVersion,
|
||||||
|
nugetconfig: nugetConfigPath,
|
||||||
|
targetframework: _testTargetframework);
|
||||||
|
|
||||||
|
transactionScope.Complete();
|
||||||
|
}
|
||||||
|
|
||||||
|
reporter.Lines.Should().BeEmpty();
|
||||||
|
|
||||||
|
FilePath executable = toolConfigurationAndExecutablePath.Executable;
|
||||||
|
File.Exists(executable.Value)
|
||||||
|
.Should()
|
||||||
|
.BeTrue(executable + " should have the executable");
|
||||||
|
|
||||||
|
File.Delete(executable.Value);
|
||||||
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData(false)]
|
[InlineData(false)]
|
||||||
[InlineData(true)]
|
[InlineData(true)]
|
||||||
|
@ -122,7 +159,7 @@ namespace Microsoft.DotNet.ToolPackage.Tests
|
||||||
var packageObtainer =
|
var packageObtainer =
|
||||||
ConstructDefaultPackageObtainer(toolsPath, reporter, testMockBehaviorIsInSync, nugetConfigPath.Value);
|
ConstructDefaultPackageObtainer(toolsPath, reporter, testMockBehaviorIsInSync, nugetConfigPath.Value);
|
||||||
|
|
||||||
ToolConfigurationAndExecutablePath toolConfigurationAndExecutableDirectory =
|
ToolConfigurationAndExecutablePath toolConfigurationAndExecutablePath =
|
||||||
packageObtainer.ObtainAndReturnExecutablePath(
|
packageObtainer.ObtainAndReturnExecutablePath(
|
||||||
packageId: TestPackageId,
|
packageId: TestPackageId,
|
||||||
packageVersion: TestPackageVersion,
|
packageVersion: TestPackageVersion,
|
||||||
|
@ -138,7 +175,7 @@ namespace Microsoft.DotNet.ToolPackage.Tests
|
||||||
/dependency2 package id/
|
/dependency2 package id/
|
||||||
/project.assets.json
|
/project.assets.json
|
||||||
*/
|
*/
|
||||||
var assetJsonPath = toolConfigurationAndExecutableDirectory
|
var assetJsonPath = toolConfigurationAndExecutablePath
|
||||||
.Executable
|
.Executable
|
||||||
.GetDirectoryPath()
|
.GetDirectoryPath()
|
||||||
.GetParentPath()
|
.GetParentPath()
|
||||||
|
@ -177,7 +214,7 @@ namespace Microsoft.DotNet.ToolPackage.Tests
|
||||||
IToolPackageObtainer packageObtainer;
|
IToolPackageObtainer packageObtainer;
|
||||||
if (testMockBehaviorIsInSync)
|
if (testMockBehaviorIsInSync)
|
||||||
{
|
{
|
||||||
packageObtainer = new ToolPackageObtainerMock();
|
packageObtainer = new ToolPackageObtainerMock(toolsPath: toolsPath);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -212,7 +249,7 @@ namespace Microsoft.DotNet.ToolPackage.Tests
|
||||||
public void GivenAllButNoPackageVersionItCanDownloadThePackage(bool testMockBehaviorIsInSync)
|
public void GivenAllButNoPackageVersionItCanDownloadThePackage(bool testMockBehaviorIsInSync)
|
||||||
{
|
{
|
||||||
var reporter = new BufferedReporter();
|
var reporter = new BufferedReporter();
|
||||||
var nugetConfigPath = WriteNugetConfigFileToPointToTheFeed();
|
FilePath nugetConfigPath = WriteNugetConfigFileToPointToTheFeed();
|
||||||
var toolsPath = Path.Combine(Directory.GetCurrentDirectory(), Path.GetRandomFileName());
|
var toolsPath = Path.Combine(Directory.GetCurrentDirectory(), Path.GetRandomFileName());
|
||||||
|
|
||||||
var packageObtainer =
|
var packageObtainer =
|
||||||
|
@ -235,35 +272,6 @@ namespace Microsoft.DotNet.ToolPackage.Tests
|
||||||
File.Delete(executable.Value);
|
File.Delete(executable.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[InlineData(false)]
|
|
||||||
[InlineData(true)]
|
|
||||||
public void GivenAllButNoPackageVersionAndInvokeTwiceItShouldNotThrow(bool testMockBehaviorIsInSync)
|
|
||||||
{
|
|
||||||
var reporter = new BufferedReporter();
|
|
||||||
var nugetConfigPath = WriteNugetConfigFileToPointToTheFeed();
|
|
||||||
var toolsPath = Path.Combine(Directory.GetCurrentDirectory(), Path.GetRandomFileName());
|
|
||||||
|
|
||||||
var packageObtainer =
|
|
||||||
ConstructDefaultPackageObtainer(toolsPath, reporter, testMockBehaviorIsInSync, nugetConfigPath.Value);
|
|
||||||
|
|
||||||
packageObtainer.ObtainAndReturnExecutablePath(
|
|
||||||
packageId: TestPackageId,
|
|
||||||
nugetconfig: nugetConfigPath,
|
|
||||||
targetframework: _testTargetframework);
|
|
||||||
|
|
||||||
reporter.Lines.Should().BeEmpty();
|
|
||||||
|
|
||||||
Action secondCall = () => packageObtainer.ObtainAndReturnExecutablePath(
|
|
||||||
packageId: TestPackageId,
|
|
||||||
nugetconfig: nugetConfigPath,
|
|
||||||
targetframework: _testTargetframework);
|
|
||||||
|
|
||||||
reporter.Lines.Should().BeEmpty();
|
|
||||||
|
|
||||||
secondCall.ShouldNotThrow();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData(false)]
|
[InlineData(false)]
|
||||||
[InlineData(true)]
|
[InlineData(true)]
|
||||||
|
@ -292,7 +300,8 @@ namespace Microsoft.DotNet.ToolPackage.Tests
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
},
|
||||||
|
toolsPath: toolsPath);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -334,6 +343,7 @@ namespace Microsoft.DotNet.ToolPackage.Tests
|
||||||
var nonExistNugetConfigFile = new FilePath("NonExistent.file");
|
var nonExistNugetConfigFile = new FilePath("NonExistent.file");
|
||||||
Action a = () =>
|
Action a = () =>
|
||||||
{
|
{
|
||||||
|
ToolConfigurationAndExecutablePath toolConfigurationAndExecutablePath =
|
||||||
packageObtainer.ObtainAndReturnExecutablePath(
|
packageObtainer.ObtainAndReturnExecutablePath(
|
||||||
packageId: TestPackageId,
|
packageId: TestPackageId,
|
||||||
packageVersion: TestPackageVersion,
|
packageVersion: TestPackageVersion,
|
||||||
|
@ -358,8 +368,13 @@ namespace Microsoft.DotNet.ToolPackage.Tests
|
||||||
var reporter = new BufferedReporter();
|
var reporter = new BufferedReporter();
|
||||||
var toolsPath = Path.Combine(Directory.GetCurrentDirectory(), Path.GetRandomFileName());
|
var toolsPath = Path.Combine(Directory.GetCurrentDirectory(), Path.GetRandomFileName());
|
||||||
|
|
||||||
var packageObtainer = ConstructDefaultPackageObtainer(toolsPath, reporter);
|
var packageObtainer = ConstructDefaultPackageObtainer(
|
||||||
var toolConfigurationAndExecutableDirectory = packageObtainer.ObtainAndReturnExecutablePath(
|
toolsPath,
|
||||||
|
reporter,
|
||||||
|
testMockBehaviorIsInSync: testMockBehaviorIsInSync,
|
||||||
|
addSourceFeedWithFilePath: GetTestLocalFeedPath());
|
||||||
|
ToolConfigurationAndExecutablePath toolConfigurationAndExecutablePath =
|
||||||
|
packageObtainer.ObtainAndReturnExecutablePath(
|
||||||
packageId: TestPackageId,
|
packageId: TestPackageId,
|
||||||
packageVersion: TestPackageVersion,
|
packageVersion: TestPackageVersion,
|
||||||
targetframework: _testTargetframework,
|
targetframework: _testTargetframework,
|
||||||
|
@ -367,7 +382,7 @@ namespace Microsoft.DotNet.ToolPackage.Tests
|
||||||
|
|
||||||
reporter.Lines.Should().BeEmpty();
|
reporter.Lines.Should().BeEmpty();
|
||||||
|
|
||||||
var executable = toolConfigurationAndExecutableDirectory.Executable;
|
var executable = toolConfigurationAndExecutablePath.Executable;
|
||||||
|
|
||||||
File.Exists(executable.Value)
|
File.Exists(executable.Value)
|
||||||
.Should()
|
.Should()
|
||||||
|
@ -376,6 +391,158 @@ namespace Microsoft.DotNet.ToolPackage.Tests
|
||||||
File.Delete(executable.Value);
|
File.Delete(executable.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(false)]
|
||||||
|
[InlineData(true)]
|
||||||
|
public void GivenFailedRestoreItCanRollBack(bool testMockBehaviorIsInSync)
|
||||||
|
{
|
||||||
|
var toolsPath = Path.Combine(Directory.GetCurrentDirectory(), Path.GetRandomFileName());
|
||||||
|
|
||||||
|
var reporter = new BufferedReporter();
|
||||||
|
var packageObtainer = ConstructDefaultPackageObtainer(toolsPath, reporter, testMockBehaviorIsInSync);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (var t = new TransactionScope())
|
||||||
|
{
|
||||||
|
packageObtainer.ObtainAndReturnExecutablePath(
|
||||||
|
packageId: "non exist package id",
|
||||||
|
packageVersion: TestPackageVersion,
|
||||||
|
targetframework: _testTargetframework);
|
||||||
|
|
||||||
|
t.Complete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (PackageObtainException)
|
||||||
|
{
|
||||||
|
// catch the intent error
|
||||||
|
}
|
||||||
|
|
||||||
|
AssertRollBack(toolsPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(false)]
|
||||||
|
[InlineData(true)]
|
||||||
|
public void GiveSucessRestoreButFailedOnNextStepItCanRollBack(bool testMockBehaviorIsInSync)
|
||||||
|
{
|
||||||
|
FilePath nugetConfigPath = WriteNugetConfigFileToPointToTheFeed();
|
||||||
|
var toolsPath = Path.Combine(Directory.GetCurrentDirectory(), Path.GetRandomFileName());
|
||||||
|
var reporter = new BufferedReporter();
|
||||||
|
var packageObtainer = ConstructDefaultPackageObtainer(toolsPath, reporter, testMockBehaviorIsInSync);
|
||||||
|
|
||||||
|
void FailedStepAfterSuccessRestore() => throw new GracefulException("simulated error");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (var t = new TransactionScope())
|
||||||
|
{
|
||||||
|
ToolConfigurationAndExecutablePath obtainAndReturnExecutablePathtransactional
|
||||||
|
= packageObtainer.ObtainAndReturnExecutablePath(
|
||||||
|
packageId: TestPackageId,
|
||||||
|
packageVersion: TestPackageVersion,
|
||||||
|
targetframework: _testTargetframework);
|
||||||
|
|
||||||
|
FailedStepAfterSuccessRestore();
|
||||||
|
t.Complete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (GracefulException)
|
||||||
|
{
|
||||||
|
// catch the simulated error
|
||||||
|
}
|
||||||
|
|
||||||
|
AssertRollBack(toolsPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(false)]
|
||||||
|
[InlineData(true)]
|
||||||
|
public void GivenAllButNoPackageVersionAndInvokeTwiceItShouldNotThrow(bool testMockBehaviorIsInSync)
|
||||||
|
{
|
||||||
|
var reporter = new BufferedReporter();
|
||||||
|
var nugetConfigPath = WriteNugetConfigFileToPointToTheFeed();
|
||||||
|
var toolsPath = Path.Combine(Directory.GetCurrentDirectory(), Path.GetRandomFileName());
|
||||||
|
|
||||||
|
var packageObtainer =
|
||||||
|
ConstructDefaultPackageObtainer(toolsPath, reporter, testMockBehaviorIsInSync, nugetConfigPath.Value);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (var t = new TransactionScope())
|
||||||
|
{
|
||||||
|
packageObtainer.ObtainAndReturnExecutablePath(
|
||||||
|
packageId: TestPackageId,
|
||||||
|
packageVersion: TestPackageVersion,
|
||||||
|
targetframework: _testTargetframework);
|
||||||
|
|
||||||
|
packageObtainer.ObtainAndReturnExecutablePath(
|
||||||
|
packageId: TestPackageId,
|
||||||
|
packageVersion: TestPackageVersion,
|
||||||
|
targetframework: _testTargetframework);
|
||||||
|
|
||||||
|
t.Complete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (PackageObtainException)
|
||||||
|
{
|
||||||
|
// catch the simulated error
|
||||||
|
}
|
||||||
|
|
||||||
|
AssertRollBack(toolsPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(false)]
|
||||||
|
[InlineData(true)]
|
||||||
|
public void GivenAllButNoPackageVersionAndInvokeTwiceInTransactionItShouldRollback(bool testMockBehaviorIsInSync)
|
||||||
|
{
|
||||||
|
var reporter = new BufferedReporter();
|
||||||
|
var nugetConfigPath = WriteNugetConfigFileToPointToTheFeed();
|
||||||
|
var toolsPath = Path.Combine(Directory.GetCurrentDirectory(), Path.GetRandomFileName());
|
||||||
|
|
||||||
|
var packageObtainer =
|
||||||
|
ConstructDefaultPackageObtainer(toolsPath, reporter, testMockBehaviorIsInSync, nugetConfigPath.Value);
|
||||||
|
|
||||||
|
packageObtainer.ObtainAndReturnExecutablePath(
|
||||||
|
packageId: TestPackageId,
|
||||||
|
nugetconfig: nugetConfigPath,
|
||||||
|
targetframework: _testTargetframework);
|
||||||
|
|
||||||
|
reporter.Lines.Should().BeEmpty();
|
||||||
|
|
||||||
|
Action secondCall = () => packageObtainer.ObtainAndReturnExecutablePath(
|
||||||
|
packageId: TestPackageId,
|
||||||
|
nugetconfig: nugetConfigPath,
|
||||||
|
targetframework: _testTargetframework);
|
||||||
|
|
||||||
|
reporter.Lines.Should().BeEmpty();
|
||||||
|
|
||||||
|
secondCall.ShouldThrow<PackageObtainException>();
|
||||||
|
|
||||||
|
Directory.Exists(Path.Combine(toolsPath, TestPackageId))
|
||||||
|
.Should().BeTrue("The result of first one is still here");
|
||||||
|
|
||||||
|
Directory.GetDirectories(Path.Combine(toolsPath, ".stage"))
|
||||||
|
.Should().BeEmpty("nothing in stage folder, already rolled back");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AssertRollBack(string toolsPath)
|
||||||
|
{
|
||||||
|
if (!Directory.Exists(toolsPath))
|
||||||
|
{
|
||||||
|
return; // nothing at all
|
||||||
|
}
|
||||||
|
|
||||||
|
Directory.GetFiles(toolsPath).Should().BeEmpty();
|
||||||
|
Directory.GetDirectories(toolsPath)
|
||||||
|
.Should().NotContain(d => !new DirectoryInfo(d).Name.Equals(".stage"),
|
||||||
|
"no broken folder, exclude stage folder");
|
||||||
|
|
||||||
|
Directory.GetDirectories(Path.Combine(toolsPath, ".stage"))
|
||||||
|
.Should().BeEmpty("nothing in stage folder");
|
||||||
|
}
|
||||||
|
|
||||||
private static readonly Func<FilePath> GetUniqueTempProjectPathEachTest = () =>
|
private static readonly Func<FilePath> GetUniqueTempProjectPathEachTest = () =>
|
||||||
{
|
{
|
||||||
var tempProjectDirectory =
|
var tempProjectDirectory =
|
||||||
|
@ -412,7 +579,7 @@ namespace Microsoft.DotNet.ToolPackage.Tests
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
}, toolsPath: toolsPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (addSourceFeedWithFilePath != null)
|
if (addSourceFeedWithFilePath != null)
|
||||||
|
@ -422,7 +589,7 @@ namespace Microsoft.DotNet.ToolPackage.Tests
|
||||||
{
|
{
|
||||||
new MockFeed
|
new MockFeed
|
||||||
{
|
{
|
||||||
Type = MockFeedType.ExplicitNugetConfig,
|
Type = MockFeedType.Source,
|
||||||
Uri = addSourceFeedWithFilePath,
|
Uri = addSourceFeedWithFilePath,
|
||||||
Packages = new List<MockFeedPackage>
|
Packages = new List<MockFeedPackage>
|
||||||
{
|
{
|
||||||
|
@ -433,10 +600,11 @@ namespace Microsoft.DotNet.ToolPackage.Tests
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
},
|
||||||
|
toolsPath: toolsPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new ToolPackageObtainerMock();
|
return new ToolPackageObtainerMock(toolsPath: toolsPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new ToolPackageObtainer(
|
return new ToolPackageObtainer(
|
||||||
|
@ -465,7 +633,6 @@ namespace Microsoft.DotNet.ToolPackage.Tests
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string GetTestLocalFeedPath() => Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "TestAssetLocalNugetFeed");
|
private static string GetTestLocalFeedPath() => Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "TestAssetLocalNugetFeed");
|
||||||
|
|
||||||
private readonly string _testTargetframework = BundledTargetFramework.GetTargetFrameworkMoniker();
|
private readonly string _testTargetframework = BundledTargetFramework.GetTargetFrameworkMoniker();
|
||||||
private const string TestPackageVersion = "1.0.4";
|
private const string TestPackageVersion = "1.0.4";
|
||||||
private const string TestPackageId = "global.tool.console.demo";
|
private const string TestPackageId = "global.tool.console.demo";
|
||||||
|
|
|
@ -2,7 +2,11 @@
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Transactions;
|
||||||
|
using Microsoft.DotNet.Cli;
|
||||||
using Microsoft.DotNet.Cli.Utils;
|
using Microsoft.DotNet.Cli.Utils;
|
||||||
using Microsoft.DotNet.ShellShim;
|
using Microsoft.DotNet.ShellShim;
|
||||||
using Microsoft.Extensions.EnvironmentAbstractions;
|
using Microsoft.Extensions.EnvironmentAbstractions;
|
||||||
|
@ -18,12 +22,56 @@ namespace Microsoft.DotNet.Tools.Tests.ComponentMocks
|
||||||
public ShellShimMakerMock(string pathToPlaceShim, IFileSystem fileSystem = null)
|
public ShellShimMakerMock(string pathToPlaceShim, IFileSystem fileSystem = null)
|
||||||
{
|
{
|
||||||
_pathToPlaceShim =
|
_pathToPlaceShim =
|
||||||
pathToPlaceShim ?? throw new ArgumentNullException(nameof(pathToPlaceShim));
|
pathToPlaceShim ??
|
||||||
|
throw new ArgumentNullException(nameof(pathToPlaceShim));
|
||||||
|
|
||||||
_fileSystem = fileSystem ?? new FileSystemWrapper();
|
_fileSystem = fileSystem ?? new FileSystemWrapper();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void EnsureCommandNameUniqueness(string shellCommandName)
|
||||||
|
{
|
||||||
|
if (_fileSystem.File.Exists(GetShimPath(shellCommandName).Value))
|
||||||
|
{
|
||||||
|
throw new GracefulException(
|
||||||
|
string.Format(CommonLocalizableStrings.FailInstallToolSameName,
|
||||||
|
shellCommandName));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void CreateShim(FilePath packageExecutable, string shellCommandName)
|
public void CreateShim(FilePath packageExecutable, string shellCommandName)
|
||||||
|
{
|
||||||
|
var createShimTransaction = new CreateShimTransaction(
|
||||||
|
createShim: locationOfShimDuringTransaction =>
|
||||||
|
{
|
||||||
|
EnsureCommandNameUniqueness(shellCommandName);
|
||||||
|
PlaceShim(packageExecutable, shellCommandName, locationOfShimDuringTransaction);
|
||||||
|
},
|
||||||
|
rollback: locationOfShimDuringTransaction =>
|
||||||
|
{
|
||||||
|
foreach (FilePath f in locationOfShimDuringTransaction)
|
||||||
|
{
|
||||||
|
if (File.Exists(f.Value))
|
||||||
|
{
|
||||||
|
File.Delete(f.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
using (var transactionScope = new TransactionScope())
|
||||||
|
{
|
||||||
|
Transaction.Current.EnlistVolatile(createShimTransaction, EnlistmentOptions.None);
|
||||||
|
createShimTransaction.CreateShim();
|
||||||
|
|
||||||
|
transactionScope.Complete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Remove(string shellCommandName)
|
||||||
|
{
|
||||||
|
File.Delete(GetShimPath(shellCommandName).Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PlaceShim(FilePath packageExecutable, string shellCommandName, List<FilePath> locationOfShimDuringTransaction)
|
||||||
{
|
{
|
||||||
var fakeshim = new FakeShim
|
var fakeshim = new FakeShim
|
||||||
{
|
{
|
||||||
|
@ -32,18 +80,20 @@ namespace Microsoft.DotNet.Tools.Tests.ComponentMocks
|
||||||
};
|
};
|
||||||
var script = JsonConvert.SerializeObject(fakeshim);
|
var script = JsonConvert.SerializeObject(fakeshim);
|
||||||
|
|
||||||
FilePath scriptPath = new FilePath(Path.Combine(_pathToPlaceShim, shellCommandName));
|
FilePath scriptPath = GetShimPath(shellCommandName);
|
||||||
_fileSystem.File.WriteAllText(scriptPath.Value, script);
|
_fileSystem.File.WriteAllText(scriptPath.Value, script);
|
||||||
|
locationOfShimDuringTransaction.Add(scriptPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void EnsureCommandNameUniqueness(string shellCommandName)
|
private FilePath GetShimPath(string shellCommandName)
|
||||||
{
|
{
|
||||||
if (_fileSystem.File.Exists(Path.Combine(_pathToPlaceShim, shellCommandName)))
|
var scriptPath = Path.Combine(_pathToPlaceShim, shellCommandName);
|
||||||
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||||
{
|
{
|
||||||
throw new GracefulException(
|
scriptPath += ".exe";
|
||||||
string.Format(CommonLocalizableStrings.FailInstallToolSameName,
|
|
||||||
shellCommandName));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return new FilePath(scriptPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class FakeShim
|
public class FakeShim
|
||||||
|
|
|
@ -5,6 +5,8 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Transactions;
|
||||||
|
using Microsoft.DotNet.Cli;
|
||||||
using Microsoft.DotNet.ToolPackage;
|
using Microsoft.DotNet.ToolPackage;
|
||||||
using Microsoft.Extensions.EnvironmentAbstractions;
|
using Microsoft.Extensions.EnvironmentAbstractions;
|
||||||
|
|
||||||
|
@ -12,20 +14,27 @@ namespace Microsoft.DotNet.Tools.Tests.ComponentMocks
|
||||||
{
|
{
|
||||||
internal class ToolPackageObtainerMock : IToolPackageObtainer
|
internal class ToolPackageObtainerMock : IToolPackageObtainer
|
||||||
{
|
{
|
||||||
private readonly Action _beforeRunObtain;
|
private readonly string _toolsPath;
|
||||||
public const string FakeEntrypointName = "SimulatorEntryPoint.dll";
|
public const string FakeEntrypointName = "SimulatorEntryPoint.dll";
|
||||||
public const string FakeCommandName = "SimulatorCommand";
|
public const string FakeCommandName = "SimulatorCommand";
|
||||||
|
private readonly Action _beforeRunObtain;
|
||||||
|
private readonly Action _duringObtain;
|
||||||
private static IFileSystem _fileSystem;
|
private static IFileSystem _fileSystem;
|
||||||
private string _fakeExecutableDirectory;
|
private string _fakeExecutableDirectory;
|
||||||
private List<MockFeed> _mockFeeds;
|
private List<MockFeed> _mockFeeds;
|
||||||
|
private string _packageIdVersionDirectory;
|
||||||
|
|
||||||
public ToolPackageObtainerMock(
|
public ToolPackageObtainerMock(
|
||||||
IFileSystem fileSystemWrapper = null,
|
IFileSystem fileSystemWrapper = null,
|
||||||
bool useDefaultFeed = true,
|
bool useDefaultFeed = true,
|
||||||
IEnumerable<MockFeed> additionalFeeds = null,
|
IEnumerable<MockFeed> additionalFeeds = null,
|
||||||
Action beforeRunObtain = null)
|
Action beforeRunObtain = null,
|
||||||
|
Action duringObtain = null,
|
||||||
|
string toolsPath = null)
|
||||||
{
|
{
|
||||||
|
_toolsPath = toolsPath ?? "toolsPath";
|
||||||
_beforeRunObtain = beforeRunObtain ?? (() => {});
|
_beforeRunObtain = beforeRunObtain ?? (() => {});
|
||||||
|
_duringObtain = duringObtain ?? (() => {});
|
||||||
_fileSystem = fileSystemWrapper ?? new FileSystemWrapper();
|
_fileSystem = fileSystemWrapper ?? new FileSystemWrapper();
|
||||||
_mockFeeds = new List<MockFeed>();
|
_mockFeeds = new List<MockFeed>();
|
||||||
|
|
||||||
|
@ -59,6 +68,18 @@ namespace Microsoft.DotNet.Tools.Tests.ComponentMocks
|
||||||
string source = null,
|
string source = null,
|
||||||
string verbosity = null)
|
string verbosity = null)
|
||||||
{
|
{
|
||||||
|
var stagedFile = Path.Combine(_toolsPath, ".stage", Path.GetRandomFileName());
|
||||||
|
bool afterStage = false;
|
||||||
|
|
||||||
|
var toolPackageObtainTransaction = new ToolPackageObtainTransaction(
|
||||||
|
obtainAndReturnExecutablePath: (_) =>
|
||||||
|
{
|
||||||
|
if (Directory.Exists(Path.Combine(_toolsPath, packageId)))
|
||||||
|
{
|
||||||
|
throw new PackageObtainException(
|
||||||
|
string.Format(CommonLocalizableStrings.ToolPackageConflictPackageId, packageId));
|
||||||
|
}
|
||||||
|
|
||||||
_beforeRunObtain();
|
_beforeRunObtain();
|
||||||
|
|
||||||
PickFeedByNugetConfig(nugetconfig);
|
PickFeedByNugetConfig(nugetconfig);
|
||||||
|
@ -77,24 +98,67 @@ namespace Microsoft.DotNet.Tools.Tests.ComponentMocks
|
||||||
packageVersion = package.Version;
|
packageVersion = package.Version;
|
||||||
targetframework = targetframework ?? "targetframework";
|
targetframework = targetframework ?? "targetframework";
|
||||||
|
|
||||||
var packageIdVersionDirectory = Path.Combine("toolPath", packageId, packageVersion);
|
_packageIdVersionDirectory = Path.Combine(_toolsPath, packageId, packageVersion);
|
||||||
|
|
||||||
_fakeExecutableDirectory = Path.Combine(packageIdVersionDirectory,
|
_fakeExecutableDirectory = Path.Combine(_packageIdVersionDirectory,
|
||||||
packageId, packageVersion, "morefolders", "tools",
|
packageId, packageVersion, "morefolders", "tools",
|
||||||
targetframework);
|
targetframework);
|
||||||
var fakeExecutable = Path.Combine(_fakeExecutableDirectory, FakeEntrypointName);
|
|
||||||
|
|
||||||
if (!_fileSystem.Directory.Exists(_fakeExecutableDirectory))
|
SimulateStageFile();
|
||||||
{
|
_duringObtain();
|
||||||
|
|
||||||
|
_fileSystem.File.Delete(stagedFile);
|
||||||
|
afterStage = true;
|
||||||
|
|
||||||
|
_fileSystem.Directory.CreateDirectory(_packageIdVersionDirectory);
|
||||||
|
_fileSystem.File.CreateEmptyFile(Path.Combine(_packageIdVersionDirectory, "project.assets.json"));
|
||||||
_fileSystem.Directory.CreateDirectory(_fakeExecutableDirectory);
|
_fileSystem.Directory.CreateDirectory(_fakeExecutableDirectory);
|
||||||
}
|
var fakeExecutable = Path.Combine(_fakeExecutableDirectory, FakeEntrypointName);
|
||||||
|
|
||||||
_fileSystem.File.CreateEmptyFile(Path.Combine(packageIdVersionDirectory, "project.assets.json"));
|
|
||||||
_fileSystem.File.CreateEmptyFile(fakeExecutable);
|
_fileSystem.File.CreateEmptyFile(fakeExecutable);
|
||||||
|
|
||||||
return new ToolConfigurationAndExecutablePath(
|
return new ToolConfigurationAndExecutablePath(
|
||||||
toolConfiguration: new ToolConfiguration(FakeCommandName, FakeEntrypointName),
|
toolConfiguration: new ToolConfiguration(FakeCommandName, FakeEntrypointName),
|
||||||
executable: new FilePath(fakeExecutable));
|
executable : new FilePath(fakeExecutable));;
|
||||||
|
},
|
||||||
|
|
||||||
|
rollback: (_) =>
|
||||||
|
{
|
||||||
|
if (afterStage == false)
|
||||||
|
{
|
||||||
|
if (_fileSystem.File.Exists(stagedFile))
|
||||||
|
{
|
||||||
|
_fileSystem.File.Delete(stagedFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (_fileSystem.Directory.Exists(Path.Combine(_toolsPath, packageId)))
|
||||||
|
{
|
||||||
|
_fileSystem.Directory.Delete(Path.Combine(_toolsPath, packageId), true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
using(var transactionScope = new TransactionScope())
|
||||||
|
{
|
||||||
|
Transaction.Current.EnlistVolatile(toolPackageObtainTransaction, EnlistmentOptions.None);
|
||||||
|
var toolConfigurationAndExecutablePath = toolPackageObtainTransaction.ObtainAndReturnExecutablePath();
|
||||||
|
|
||||||
|
transactionScope.Complete();
|
||||||
|
return toolConfigurationAndExecutablePath;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SimulateStageFile()
|
||||||
|
{
|
||||||
|
var stageDirectory = Path.Combine(_toolsPath, ".stage");
|
||||||
|
if (!_fileSystem.Directory.Exists(stageDirectory))
|
||||||
|
{
|
||||||
|
_fileSystem.Directory.CreateDirectory(stageDirectory);
|
||||||
|
}
|
||||||
|
|
||||||
|
_fileSystem.File.CreateEmptyFile(Path.Combine(stageDirectory, "stagedfile"));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void PickFeedBySource(string source)
|
private void PickFeedBySource(string source)
|
||||||
|
|
|
@ -106,6 +106,16 @@ namespace Microsoft.Extensions.DependencyModel.Tests
|
||||||
{
|
{
|
||||||
_files[path] = content;
|
_files[path] = content;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Delete(string path)
|
||||||
|
{
|
||||||
|
if (!Exists(path))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_files.Remove(path);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class DirectoryMock : IDirectory
|
private class DirectoryMock : IDirectory
|
||||||
|
@ -143,6 +153,22 @@ namespace Microsoft.Extensions.DependencyModel.Tests
|
||||||
{
|
{
|
||||||
_files.Add(path, path);
|
_files.Add(path, path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Delete(string path, bool recursive)
|
||||||
|
{
|
||||||
|
if (!recursive && Exists(path) == true)
|
||||||
|
{
|
||||||
|
if (_files.Keys.Where(k => k.StartsWith(path)).Count() > 1)
|
||||||
|
{
|
||||||
|
throw new IOException("The directory is not empty");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var k in _files.Keys.Where(k => k.StartsWith(path)).ToList())
|
||||||
|
{
|
||||||
|
_files.Remove(k);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class TemporaryDirectoryMock : ITemporaryDirectoryMock
|
private class TemporaryDirectoryMock : ITemporaryDirectoryMock
|
||||||
|
|
|
@ -19,6 +19,7 @@ using Newtonsoft.Json;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
using Parser = Microsoft.DotNet.Cli.Parser;
|
using Parser = Microsoft.DotNet.Cli.Parser;
|
||||||
using LocalizableStrings = Microsoft.DotNet.Tools.Install.Tool.LocalizableStrings;
|
using LocalizableStrings = Microsoft.DotNet.Tools.Install.Tool.LocalizableStrings;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
namespace Microsoft.DotNet.Tests.InstallToolCommandTests
|
namespace Microsoft.DotNet.Tests.InstallToolCommandTests
|
||||||
{
|
{
|
||||||
|
@ -32,20 +33,22 @@ namespace Microsoft.DotNet.Tests.InstallToolCommandTests
|
||||||
private readonly ParseResult _parseResult;
|
private readonly ParseResult _parseResult;
|
||||||
private readonly BufferedReporter _reporter;
|
private readonly BufferedReporter _reporter;
|
||||||
private const string PathToPlaceShim = "pathToPlace";
|
private const string PathToPlaceShim = "pathToPlace";
|
||||||
|
private const string PathToPlacePackages = PathToPlaceShim + "pkg";
|
||||||
|
private const string PackageId = "global.tool.console.demo";
|
||||||
|
|
||||||
public InstallToolCommandTests()
|
public InstallToolCommandTests()
|
||||||
{
|
{
|
||||||
_fileSystemWrapper = new FileSystemMockBuilder().Build();
|
_fileSystemWrapper = new FileSystemMockBuilder().Build();
|
||||||
_toolPackageObtainerMock = new ToolPackageObtainerMock(_fileSystemWrapper);
|
_toolPackageObtainerMock = new ToolPackageObtainerMock(_fileSystemWrapper, toolsPath: PathToPlacePackages);
|
||||||
_shellShimMakerMock = new ShellShimMakerMock(PathToPlaceShim, _fileSystemWrapper);
|
_shellShimMakerMock = new ShellShimMakerMock(PathToPlaceShim, _fileSystemWrapper);
|
||||||
_reporter = new BufferedReporter();
|
_reporter = new BufferedReporter();
|
||||||
_environmentPathInstructionMock =
|
_environmentPathInstructionMock =
|
||||||
new EnvironmentPathInstructionMock(_reporter, PathToPlaceShim);
|
new EnvironmentPathInstructionMock(_reporter, PathToPlaceShim);
|
||||||
|
|
||||||
ParseResult result = Parser.Instance.Parse("dotnet install tool -g global.tool.console.demo");
|
ParseResult result = Parser.Instance.Parse($"dotnet install tool -g {PackageId}");
|
||||||
_appliedCommand = result["dotnet"]["install"]["tool"];
|
_appliedCommand = result["dotnet"]["install"]["tool"];
|
||||||
var parser = Parser.Instance;
|
var parser = Parser.Instance;
|
||||||
_parseResult = parser.ParseFrom("dotnet install", new[] {"tool", "global.tool.console.demo"});
|
_parseResult = parser.ParseFrom("dotnet install", new[] {"tool", PackageId});
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
@ -60,12 +63,9 @@ namespace Microsoft.DotNet.Tests.InstallToolCommandTests
|
||||||
installToolCommand.Execute().Should().Be(0);
|
installToolCommand.Execute().Should().Be(0);
|
||||||
|
|
||||||
// It is hard to simulate shell behavior. Only Assert shim can point to executable dll
|
// It is hard to simulate shell behavior. Only Assert shim can point to executable dll
|
||||||
_fileSystemWrapper.File.Exists(Path.Combine("pathToPlace", ToolPackageObtainerMock.FakeCommandName))
|
_fileSystemWrapper.File.Exists(ExpectedCommandPath()).Should().BeTrue();
|
||||||
.Should().BeTrue();
|
|
||||||
var deserializedFakeShim = JsonConvert.DeserializeObject<ShellShimMakerMock.FakeShim>(
|
var deserializedFakeShim = JsonConvert.DeserializeObject<ShellShimMakerMock.FakeShim>(
|
||||||
_fileSystemWrapper.File.ReadAllText(
|
_fileSystemWrapper.File.ReadAllText(ExpectedCommandPath()));
|
||||||
Path.Combine("pathToPlace",
|
|
||||||
ToolPackageObtainerMock.FakeCommandName)));
|
|
||||||
_fileSystemWrapper.File.Exists(deserializedFakeShim.ExecutablePath).Should().BeTrue();
|
_fileSystemWrapper.File.Exists(deserializedFakeShim.ExecutablePath).Should().BeTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,11 +73,10 @@ namespace Microsoft.DotNet.Tests.InstallToolCommandTests
|
||||||
public void WhenRunWithPackageIdWithSourceItShouldCreateValidShim()
|
public void WhenRunWithPackageIdWithSourceItShouldCreateValidShim()
|
||||||
{
|
{
|
||||||
const string sourcePath = "http://mysouce.com";
|
const string sourcePath = "http://mysouce.com";
|
||||||
ParseResult result = Parser.Instance.Parse($"dotnet install tool -g global.tool.console.demo --source {sourcePath}");
|
ParseResult result = Parser.Instance.Parse($"dotnet install tool -g {PackageId} --source {sourcePath}");
|
||||||
AppliedOption appliedCommand = result["dotnet"]["install"]["tool"];
|
AppliedOption appliedCommand = result["dotnet"]["install"]["tool"];
|
||||||
const string packageId = "global.tool.console.demo";
|
|
||||||
ParseResult parseResult =
|
ParseResult parseResult =
|
||||||
Parser.Instance.ParseFrom("dotnet install", new[] {"tool", packageId, "--source", sourcePath});
|
Parser.Instance.ParseFrom("dotnet install", new[] { "tool", PackageId, "--source", sourcePath });
|
||||||
|
|
||||||
var installToolCommand = new InstallToolCommand(appliedCommand,
|
var installToolCommand = new InstallToolCommand(appliedCommand,
|
||||||
parseResult,
|
parseResult,
|
||||||
|
@ -91,7 +90,7 @@ namespace Microsoft.DotNet.Tests.InstallToolCommandTests
|
||||||
{
|
{
|
||||||
new MockFeedPackage
|
new MockFeedPackage
|
||||||
{
|
{
|
||||||
PackageId = packageId,
|
PackageId = PackageId,
|
||||||
Version = "1.0.4"
|
Version = "1.0.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -103,13 +102,11 @@ namespace Microsoft.DotNet.Tests.InstallToolCommandTests
|
||||||
installToolCommand.Execute().Should().Be(0);
|
installToolCommand.Execute().Should().Be(0);
|
||||||
|
|
||||||
// It is hard to simulate shell behavior. Only Assert shim can point to executable dll
|
// It is hard to simulate shell behavior. Only Assert shim can point to executable dll
|
||||||
_fileSystemWrapper.File.Exists(Path.Combine("pathToPlace", ToolPackageObtainerMock.FakeCommandName))
|
_fileSystemWrapper.File.Exists(ExpectedCommandPath())
|
||||||
.Should().BeTrue();
|
.Should().BeTrue();
|
||||||
ShellShimMakerMock.FakeShim deserializedFakeShim =
|
ShellShimMakerMock.FakeShim deserializedFakeShim =
|
||||||
JsonConvert.DeserializeObject<ShellShimMakerMock.FakeShim>(
|
JsonConvert.DeserializeObject<ShellShimMakerMock.FakeShim>(
|
||||||
_fileSystemWrapper.File.ReadAllText(
|
_fileSystemWrapper.File.ReadAllText(ExpectedCommandPath()));
|
||||||
Path.Combine("pathToPlace",
|
|
||||||
ToolPackageObtainerMock.FakeCommandName)));
|
|
||||||
_fileSystemWrapper.File.Exists(deserializedFakeShim.ExecutablePath).Should().BeTrue();
|
_fileSystemWrapper.File.Exists(deserializedFakeShim.ExecutablePath).Should().BeTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -157,6 +154,49 @@ namespace Microsoft.DotNet.Tests.InstallToolCommandTests
|
||||||
.Contain(string.Format(LocalizableStrings.ToolInstallationFailed, "global.tool.console.demo"));
|
.Contain(string.Format(LocalizableStrings.ToolInstallationFailed, "global.tool.console.demo"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GivenFailedPackageObtainWhenRunWithPackageIdItShouldHaveNoBrokenFolderOnDisk()
|
||||||
|
{
|
||||||
|
var toolPackageObtainerSimulatorThatThrows
|
||||||
|
= new ToolPackageObtainerMock(
|
||||||
|
_fileSystemWrapper, true, null,
|
||||||
|
duringObtain: () => throw new PackageObtainException("Simulated error"),
|
||||||
|
toolsPath: PathToPlacePackages);
|
||||||
|
var installToolCommand = new InstallToolCommand(
|
||||||
|
_appliedCommand,
|
||||||
|
_parseResult,
|
||||||
|
toolPackageObtainerSimulatorThatThrows,
|
||||||
|
_shellShimMakerMock,
|
||||||
|
_environmentPathInstructionMock,
|
||||||
|
_reporter);
|
||||||
|
|
||||||
|
installToolCommand.Execute();
|
||||||
|
|
||||||
|
_fileSystemWrapper.Directory.Exists(Path.Combine(PathToPlacePackages, PackageId)).Should().BeFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GivenCreateShimItShouldHaveNoBrokenFolderOnDisk()
|
||||||
|
{
|
||||||
|
_fileSystemWrapper.File.CreateEmptyFile(ExpectedCommandPath()); // Create conflict shim
|
||||||
|
var toolPackageObtainerSimulatorThatThrows
|
||||||
|
= new ToolPackageObtainerMock(
|
||||||
|
_fileSystemWrapper, true, null,
|
||||||
|
toolsPath: PathToPlacePackages);
|
||||||
|
var installToolCommand = new InstallToolCommand(
|
||||||
|
_appliedCommand,
|
||||||
|
_parseResult,
|
||||||
|
toolPackageObtainerSimulatorThatThrows,
|
||||||
|
_shellShimMakerMock,
|
||||||
|
_environmentPathInstructionMock,
|
||||||
|
_reporter);
|
||||||
|
|
||||||
|
Action a = () => installToolCommand.Execute();
|
||||||
|
a.ShouldThrow<GracefulException>();
|
||||||
|
|
||||||
|
_fileSystemWrapper.Directory.Exists(Path.Combine(PathToPlacePackages, PackageId)).Should().BeFalse();
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void GivenInCorrectToolConfigurationWhenRunWithPackageIdItShouldFail()
|
public void GivenInCorrectToolConfigurationWhenRunWithPackageIdItShouldFail()
|
||||||
{
|
{
|
||||||
|
@ -208,5 +248,13 @@ namespace Microsoft.DotNet.Tests.InstallToolCommandTests
|
||||||
.Single().Should()
|
.Single().Should()
|
||||||
.Contain(string.Format(LocalizableStrings.InstallationSucceeded, "SimulatorCommand"));
|
.Contain(string.Format(LocalizableStrings.InstallationSucceeded, "SimulatorCommand"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string ExpectedCommandPath()
|
||||||
|
{
|
||||||
|
var extension = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? ".exe" : string.Empty;
|
||||||
|
return Path.Combine(
|
||||||
|
"pathToPlace",
|
||||||
|
ToolPackageObtainerMock.FakeCommandName + extension);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue