From b0ee5db411a7c257757b6c70e0b9ae0ceb58ed5f Mon Sep 17 00:00:00 2001 From: William Li Date: Tue, 10 Apr 2018 15:42:50 -0700 Subject: [PATCH] consume bring your own shim(byos) (#9018) If there are shims packaged by convention in nupkg. Shim Repository will simply copy it to the right location. The query interface ToolPackageInstance will be in charge of finding the shim folder and filter the right RID. Shim Repository will pick the right file after the folder is located since Shim Repository knows the shim name and it also book keep the files at uninstallation. During development, due to the wrong adapter level. The mock duplicated too much logic. So, I corrected the abstraction level to lower (only create shim). And replaced the existing mock with a much smaller one without any atomic control and file move, copy logic. At the same time. The chmod, which is a IO action, causes problem during tests. So I added adapter layer to it and put it in Util. --- .../CommandPermission.cs | 29 ++++ .../FilePermissionSettingException.cs | 28 ++++ .../IFilePermissionSetter.cs | 7 + .../FileWrapper.cs | 5 + .../IFile.cs | 2 + src/dotnet/CommonLocalizableStrings.resx | 6 + src/dotnet/ShellShim/AppHostShimMaker.cs | 53 +++++++ .../ShellShim/IApphostShellShimMaker.cs | 9 ++ src/dotnet/ShellShim/IShellShimRepository.cs | 3 +- src/dotnet/ShellShim/ShellShimRepository.cs | 116 ++++++++-------- src/dotnet/ToolPackage/IToolPackage.cs | 2 + .../ToolPackage/LockFileMatchChecker.cs | 67 ++++++--- src/dotnet/ToolPackage/ToolPackageInstance.cs | 84 +++++++++-- .../dotnet-tool/install/ToolInstallCommand.cs | 2 +- .../xlf/CommonLocalizableStrings.cs.xlf | 10 ++ .../xlf/CommonLocalizableStrings.de.xlf | 10 ++ .../xlf/CommonLocalizableStrings.es.xlf | 10 ++ .../xlf/CommonLocalizableStrings.fr.xlf | 10 ++ .../xlf/CommonLocalizableStrings.it.xlf | 10 ++ .../xlf/CommonLocalizableStrings.ja.xlf | 10 ++ .../xlf/CommonLocalizableStrings.ko.xlf | 10 ++ .../xlf/CommonLocalizableStrings.pl.xlf | 10 ++ .../xlf/CommonLocalizableStrings.pt-BR.xlf | 10 ++ .../xlf/CommonLocalizableStrings.ru.xlf | 10 ++ .../xlf/CommonLocalizableStrings.tr.xlf | 10 ++ .../xlf/CommonLocalizableStrings.zh-Hans.xlf | 10 ++ .../xlf/CommonLocalizableStrings.zh-Hant.xlf | 10 ++ ...nAFunctionReturnStringAndFakeFileSystem.cs | 5 + .../GivenANuGetCacheSentinel.cs | 5 + .../ShellShimRepositoryTests.cs | 80 ++++++++++- .../LockFileMatcherTests.cs | 16 ++- .../Microsoft.DotNet.ToolPackage.Tests.csproj | 18 +++ .../DotnetToolSettings.xml | 6 + .../SampleGlobalToolWithShim/Program.cs | 14 ++ .../consoledemo.csproj | 7 + .../SampleGlobalToolWithShim/dummyshim | 1 + .../SampleGlobalToolWithShim/dummyshim.exe | 1 + .../includepublish.nuspec | 20 +++ .../ToolPackageInstanceTests.cs | 130 ++++++++++++++++++ .../AppHostShellShimMakerMock.cs | 38 +++++ .../ShellShimRepositoryMock.cs | 104 -------------- .../ToolPackageInstallerMock.cs | 10 +- .../ToolPackageMock.cs | 13 +- .../Mock/FileSystemMockBuilder.cs | 14 ++ .../CommandTests/ToolInstallCommandTests.cs | 53 ++++++- .../CommandTests/ToolUninstallCommandTests.cs | 11 +- .../CommandTests/ToolUpdateCommandTests.cs | 17 ++- 47 files changed, 898 insertions(+), 208 deletions(-) create mode 100644 src/Microsoft.DotNet.Cli.Utils/CommandPermission.cs create mode 100644 src/Microsoft.DotNet.Cli.Utils/FilePermissionSettingException.cs create mode 100644 src/Microsoft.DotNet.Cli.Utils/IFilePermissionSetter.cs create mode 100644 src/dotnet/ShellShim/AppHostShimMaker.cs create mode 100644 src/dotnet/ShellShim/IApphostShellShimMaker.cs create mode 100644 test/Microsoft.DotNet.ToolPackage.Tests/SampleGlobalToolWithShim/DotnetToolSettings.xml create mode 100644 test/Microsoft.DotNet.ToolPackage.Tests/SampleGlobalToolWithShim/Program.cs create mode 100644 test/Microsoft.DotNet.ToolPackage.Tests/SampleGlobalToolWithShim/consoledemo.csproj create mode 100644 test/Microsoft.DotNet.ToolPackage.Tests/SampleGlobalToolWithShim/dummyshim create mode 100644 test/Microsoft.DotNet.ToolPackage.Tests/SampleGlobalToolWithShim/dummyshim.exe create mode 100644 test/Microsoft.DotNet.ToolPackage.Tests/SampleGlobalToolWithShim/includepublish.nuspec create mode 100644 test/Microsoft.DotNet.ToolPackage.Tests/ToolPackageInstanceTests.cs create mode 100644 test/Microsoft.DotNet.Tools.Tests.ComponentMocks/AppHostShellShimMakerMock.cs delete mode 100644 test/Microsoft.DotNet.Tools.Tests.ComponentMocks/ShellShimRepositoryMock.cs diff --git a/src/Microsoft.DotNet.Cli.Utils/CommandPermission.cs b/src/Microsoft.DotNet.Cli.Utils/CommandPermission.cs new file mode 100644 index 000000000..d26d2f640 --- /dev/null +++ b/src/Microsoft.DotNet.Cli.Utils/CommandPermission.cs @@ -0,0 +1,29 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Runtime.InteropServices; + +namespace Microsoft.DotNet.Cli.Utils +{ + internal class FilePermissionSetter : IFilePermissionSetter + { + public void SetUserExecutionPermission(string path) + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return; + } + + CommandResult result = new CommandFactory() + .Create("chmod", new[] { "u+x", path }) + .CaptureStdOut() + .CaptureStdErr() + .Execute(); + + if (result.ExitCode != 0) + { + throw new FilePermissionSettingException(result.StdErr); + } + } + } +} diff --git a/src/Microsoft.DotNet.Cli.Utils/FilePermissionSettingException.cs b/src/Microsoft.DotNet.Cli.Utils/FilePermissionSettingException.cs new file mode 100644 index 000000000..b7aaaaba8 --- /dev/null +++ b/src/Microsoft.DotNet.Cli.Utils/FilePermissionSettingException.cs @@ -0,0 +1,28 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Runtime.Serialization; + +namespace Microsoft.DotNet.Cli.Utils +{ + [Serializable] + internal class FilePermissionSettingException : Exception + { + public FilePermissionSettingException() + { + } + + public FilePermissionSettingException(string message) : base(message) + { + } + + public FilePermissionSettingException(string message, Exception innerException) : base(message, innerException) + { + } + + protected FilePermissionSettingException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } + } +} diff --git a/src/Microsoft.DotNet.Cli.Utils/IFilePermissionSetter.cs b/src/Microsoft.DotNet.Cli.Utils/IFilePermissionSetter.cs new file mode 100644 index 000000000..9753fc232 --- /dev/null +++ b/src/Microsoft.DotNet.Cli.Utils/IFilePermissionSetter.cs @@ -0,0 +1,7 @@ +namespace Microsoft.DotNet.Cli.Utils +{ + internal interface IFilePermissionSetter + { + void SetUserExecutionPermission(string path); + } +} diff --git a/src/Microsoft.DotNet.InternalAbstractions/FileWrapper.cs b/src/Microsoft.DotNet.InternalAbstractions/FileWrapper.cs index 512037f33..c46f8e37a 100644 --- a/src/Microsoft.DotNet.InternalAbstractions/FileWrapper.cs +++ b/src/Microsoft.DotNet.InternalAbstractions/FileWrapper.cs @@ -51,6 +51,11 @@ namespace Microsoft.Extensions.EnvironmentAbstractions File.Move(source, destination); } + public void Copy(string sourceFileName, string destFileName) + { + File.Copy(sourceFileName, destFileName); + } + public void Delete(string path) { File.Delete(path); diff --git a/src/Microsoft.DotNet.InternalAbstractions/IFile.cs b/src/Microsoft.DotNet.InternalAbstractions/IFile.cs index a04f70dcd..044297b6e 100644 --- a/src/Microsoft.DotNet.InternalAbstractions/IFile.cs +++ b/src/Microsoft.DotNet.InternalAbstractions/IFile.cs @@ -27,6 +27,8 @@ namespace Microsoft.Extensions.EnvironmentAbstractions void Move(string source, string destination); + void Copy(string source, string destination); + void Delete(string path); } } diff --git a/src/dotnet/CommonLocalizableStrings.resx b/src/dotnet/CommonLocalizableStrings.resx index ad89d2c03..f4c170343 100644 --- a/src/dotnet/CommonLocalizableStrings.resx +++ b/src/dotnet/CommonLocalizableStrings.resx @@ -640,4 +640,10 @@ setx PATH "%PATH%;{0}" Format version is missing. This tool may not be supported in this SDK version. Please contact the author of the tool. + + More than one packaged shim is available: {0}. + + + Failed to read NuGet LockFile for tool package '{0}': {1} + diff --git a/src/dotnet/ShellShim/AppHostShimMaker.cs b/src/dotnet/ShellShim/AppHostShimMaker.cs new file mode 100644 index 000000000..9b5eaf2de --- /dev/null +++ b/src/dotnet/ShellShim/AppHostShimMaker.cs @@ -0,0 +1,53 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.IO; +using System.Runtime.InteropServices; +using Microsoft.DotNet.Cli.Utils; +using Microsoft.DotNet.PlatformAbstractions; +using Microsoft.DotNet.Tools.Common; +using Microsoft.Extensions.EnvironmentAbstractions; + +namespace Microsoft.DotNet.ShellShim +{ + internal class AppHostShellShimMaker : IAppHostShellShimMaker + { + private const string ApphostNameWithoutExtension = "apphost"; + private readonly string _appHostSourceDirectory; + private readonly IFilePermissionSetter _filePermissionSetter; + + public AppHostShellShimMaker(string appHostSourceDirectory = null, IFilePermissionSetter filePermissionSetter = null) + { + _appHostSourceDirectory = + appHostSourceDirectory + ?? Path.Combine(ApplicationEnvironment.ApplicationBasePath, "AppHostTemplate"); + + _filePermissionSetter = + filePermissionSetter + ?? new FilePermissionSetter(); + } + + public void CreateApphostShellShim(FilePath entryPoint, FilePath shimPath) + { + string appHostSourcePath; + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + appHostSourcePath = Path.Combine(_appHostSourceDirectory, ApphostNameWithoutExtension + ".exe"); + } + else + { + appHostSourcePath = Path.Combine(_appHostSourceDirectory, ApphostNameWithoutExtension); + } + + var appHostDestinationFilePath = shimPath.Value; + var appBinaryFilePath = PathUtility.GetRelativePath(appHostDestinationFilePath, entryPoint.Value); + + EmbedAppNameInHost.EmbedAndReturnModifiedAppHostPath( + appHostSourceFilePath: appHostSourcePath, + appHostDestinationFilePath: appHostDestinationFilePath, + appBinaryFilePath: appBinaryFilePath); + + _filePermissionSetter.SetUserExecutionPermission(appHostDestinationFilePath); + } + } +} diff --git a/src/dotnet/ShellShim/IApphostShellShimMaker.cs b/src/dotnet/ShellShim/IApphostShellShimMaker.cs new file mode 100644 index 000000000..962e68881 --- /dev/null +++ b/src/dotnet/ShellShim/IApphostShellShimMaker.cs @@ -0,0 +1,9 @@ +using Microsoft.Extensions.EnvironmentAbstractions; + +namespace Microsoft.DotNet.ShellShim +{ + internal interface IAppHostShellShimMaker + { + void CreateApphostShellShim(FilePath entryPoint, FilePath shimPath); + } +} diff --git a/src/dotnet/ShellShim/IShellShimRepository.cs b/src/dotnet/ShellShim/IShellShimRepository.cs index 737b548df..e8049fa6e 100644 --- a/src/dotnet/ShellShim/IShellShimRepository.cs +++ b/src/dotnet/ShellShim/IShellShimRepository.cs @@ -1,12 +1,13 @@ // Copyright (c) .NET Foundation and contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +using System.Collections.Generic; using Microsoft.Extensions.EnvironmentAbstractions; namespace Microsoft.DotNet.ShellShim { internal interface IShellShimRepository { - void CreateShim(FilePath targetExecutablePath, string commandName); + void CreateShim(FilePath targetExecutablePath, string commandName, IReadOnlyList packagedShims = null); void RemoveShim(string commandName); } diff --git a/src/dotnet/ShellShim/ShellShimRepository.cs b/src/dotnet/ShellShim/ShellShimRepository.cs index a7b890525..121582cf6 100644 --- a/src/dotnet/ShellShim/ShellShimRepository.cs +++ b/src/dotnet/ShellShim/ShellShimRepository.cs @@ -21,16 +21,24 @@ namespace Microsoft.DotNet.ShellShim private const string ApphostNameWithoutExtension = "apphost"; private readonly DirectoryPath _shimsDirectory; - private readonly string _appHostSourceDirectory; + private readonly IFileSystem _fileSystem; + private readonly IAppHostShellShimMaker _appHostShellShimMaker; + private readonly IFilePermissionSetter _filePermissionSetter; - public ShellShimRepository(DirectoryPath shimsDirectory, string appHostSourcePath = null) + public ShellShimRepository( + DirectoryPath shimsDirectory, + string appHostSourceDirectory = null, + IFileSystem fileSystem = null, + IAppHostShellShimMaker appHostShellShimMaker = null, + IFilePermissionSetter filePermissionSetter = null) { _shimsDirectory = shimsDirectory; - _appHostSourceDirectory = appHostSourcePath ?? Path.Combine(ApplicationEnvironment.ApplicationBasePath, - "AppHostTemplate"); + _fileSystem = fileSystem ?? new FileSystemWrapper(); + _appHostShellShimMaker = appHostShellShimMaker ?? new AppHostShellShimMaker(appHostSourceDirectory: appHostSourceDirectory); + _filePermissionSetter = filePermissionSetter ?? new FilePermissionSetter(); } - public void CreateShim(FilePath targetExecutablePath, string commandName) + public void CreateShim(FilePath targetExecutablePath, string commandName, IReadOnlyList packagedShims = null) { if (string.IsNullOrEmpty(targetExecutablePath.Value)) { @@ -54,19 +62,27 @@ namespace Microsoft.DotNet.ShellShim { try { - if (!Directory.Exists(_shimsDirectory.Value)) + if (!_fileSystem.Directory.Exists(_shimsDirectory.Value)) { - Directory.CreateDirectory(_shimsDirectory.Value); + _fileSystem.Directory.CreateDirectory(_shimsDirectory.Value); } - CreateApphostShim( - commandName, - entryPoint: targetExecutablePath); - - if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + if (TryGetPackagedShim(packagedShims, commandName, out FilePath? packagedShim)) { - SetUserExecutionPermission(GetShimPath(commandName)); + _fileSystem.File.Copy(packagedShim.Value.Value, GetShimPath(commandName).Value); + _filePermissionSetter.SetUserExecutionPermission(GetShimPath(commandName).Value); } + else + { + _appHostShellShimMaker.CreateApphostShellShim( + targetExecutablePath, + GetShimPath(commandName)); + } + } + catch (FilePermissionSettingException ex) + { + throw new ShellShimException( + string.Format(CommonLocalizableStrings.FailedSettingShimPermissions, ex.Message)); } catch (Exception ex) when (ex is UnauthorizedAccessException || ex is IOException) { @@ -80,7 +96,7 @@ namespace Microsoft.DotNet.ShellShim } }, rollback: () => { - foreach (var file in GetShimFiles(commandName).Where(f => File.Exists(f.Value))) + foreach (var file in GetShimFiles(commandName).Where(f => _fileSystem.File.Exists(f.Value))) { File.Delete(file.Value); } @@ -94,10 +110,10 @@ namespace Microsoft.DotNet.ShellShim action: () => { try { - foreach (var file in GetShimFiles(commandName).Where(f => File.Exists(f.Value))) + foreach (var file in GetShimFiles(commandName).Where(f => _fileSystem.File.Exists(f.Value))) { var tempPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); - File.Move(file.Value, tempPath); + _fileSystem.File.Move(file.Value, tempPath); files[file.Value] = tempPath; } } @@ -115,38 +131,17 @@ namespace Microsoft.DotNet.ShellShim commit: () => { foreach (var value in files.Values) { - File.Delete(value); + _fileSystem.File.Delete(value); } }, rollback: () => { foreach (var kvp in files) { - File.Move(kvp.Value, kvp.Key); + _fileSystem.File.Move(kvp.Value, kvp.Key); } }); } - private void CreateApphostShim(string commandName, FilePath entryPoint) - { - string appHostSourcePath; - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - appHostSourcePath = Path.Combine(_appHostSourceDirectory, ApphostNameWithoutExtension + ".exe"); - } - else - { - appHostSourcePath = Path.Combine(_appHostSourceDirectory, ApphostNameWithoutExtension); - } - - var appHostDestinationFilePath = GetShimPath(commandName).Value; - var appBinaryFilePath = PathUtility.GetRelativePath(appHostDestinationFilePath, entryPoint.Value); - - EmbedAppNameInHost.EmbedAndReturnModifiedAppHostPath( - appHostSourceFilePath: appHostSourcePath, - appHostDestinationFilePath: appHostDestinationFilePath, - appBinaryFilePath: appBinaryFilePath); - } - private class StartupOptions { public string appRoot { get; set; } @@ -159,7 +154,7 @@ namespace Microsoft.DotNet.ShellShim private bool ShimExists(string commandName) { - return GetShimFiles(commandName).Any(p => File.Exists(p.Value)); + return GetShimFiles(commandName).Any(p => _fileSystem.File.Exists(p.Value)); } private IEnumerable GetShimFiles(string commandName) @@ -184,24 +179,37 @@ namespace Microsoft.DotNet.ShellShim } } - private static void SetUserExecutionPermission(FilePath path) + private bool TryGetPackagedShim( + IReadOnlyList packagedShims, + string commandName, + out FilePath? packagedShim) { - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + packagedShim = null; + + if (packagedShims != null && packagedShims.Count > 0) { - return; + FilePath[] candidatepackagedShim = + packagedShims + .Where(s => string.Equals( + Path.GetFileName(s.Value), + Path.GetFileName(GetShimPath(commandName).Value))).ToArray(); + + if (candidatepackagedShim.Length > 1) + { + throw new ShellShimException( + string.Format( + CommonLocalizableStrings.MoreThanOnePackagedShimAvailable, + string.Join(';', candidatepackagedShim))); + } + + if (candidatepackagedShim.Length == 1) + { + packagedShim = candidatepackagedShim.Single(); + return true; + } } - CommandResult result = new CommandFactory() - .Create("chmod", new[] { "u+x", path.Value }) - .CaptureStdOut() - .CaptureStdErr() - .Execute(); - - if (result.ExitCode != 0) - { - throw new ShellShimException( - string.Format(CommonLocalizableStrings.FailedSettingShimPermissions, result.StdErr)); - } + return false; } } } diff --git a/src/dotnet/ToolPackage/IToolPackage.cs b/src/dotnet/ToolPackage/IToolPackage.cs index ebf351487..0b0a16c1e 100644 --- a/src/dotnet/ToolPackage/IToolPackage.cs +++ b/src/dotnet/ToolPackage/IToolPackage.cs @@ -20,6 +20,8 @@ namespace Microsoft.DotNet.ToolPackage IEnumerable Warnings { get; } + IReadOnlyList PackagedShims { get; } + void Uninstall(); } } diff --git a/src/dotnet/ToolPackage/LockFileMatchChecker.cs b/src/dotnet/ToolPackage/LockFileMatchChecker.cs index efa4af6b6..03d0a6db5 100644 --- a/src/dotnet/ToolPackage/LockFileMatchChecker.cs +++ b/src/dotnet/ToolPackage/LockFileMatchChecker.cs @@ -1,6 +1,7 @@ // Copyright (c) .NET Foundation and contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +using System.Collections.Generic; using System.Linq; using NuGet.ProjectModel; @@ -22,36 +23,60 @@ namespace Microsoft.DotNet.ToolPackage string[] entryPointPathInArray = SplitPathByDirectorySeparator(targetRelativeFilePath); return entryPointPathInArray.Length >= 1 - && PathInLockFileDirectoriesStartWithToolsAndFollowsTwoSubFolder() - && SubPathMatchesTargetFilePath(); + && PathInLockFileDirectoriesStartWithToolsAndFollowsTwoSubFolder( + pathInLockFilePathInArray, + entryPointPathInArray) + && SubPathMatchesTargetFilePath(pathInLockFilePathInArray, entryPointPathInArray); + } - bool SubPathMatchesTargetFilePath() + /// + /// Check if LockFileItem is under targetRelativePath directory. + /// The path in LockFileItem is in pattern tools/TFM/RID/my/tool.dll. Tools/TFM/RID is selected by NuGet. + /// And there will be only one TFM/RID combination. + /// When "my/folder/of/tool/tools.dll" part under targetRelativePath "my/folder/of" or "my/folder", return true. + /// + internal static bool MatchesDirectoryPath(LockFileItem lockFileItem, string targetRelativePath) + { + string[] pathInLockFilePathInArray = SplitPathByDirectorySeparator(lockFileItem.Path); + string[] targetDirectoryPathInArray = SplitPathByDirectorySeparator(targetRelativePath); + + return pathInLockFilePathInArray[0] == "tools" + && SubPathMatchesTargetFilePath(pathInLockFilePathInArray, targetDirectoryPathInArray); + } + + private static bool SubPathMatchesTargetFilePath(string[] pathInLockFilePathInArray, string[] targetInArray) + { + string[] pathAfterToolsTfmRid = pathInLockFilePathInArray.Skip(3).ToArray(); + return !targetInArray + .Where((directoryOnEveryLevel, i) => directoryOnEveryLevel != pathAfterToolsTfmRid[i]) + .Any(); + } + + private static bool PathInLockFileDirectoriesStartWithToolsAndFollowsTwoSubFolder( + string[] pathInLockFilePathInArray, + string[] targetInArray) + { + if (pathInLockFilePathInArray.Length - targetInArray.Length != 3) { - string[] pathAfterToolsTfmRid = pathInLockFilePathInArray.Skip(3).ToArray(); - return !pathAfterToolsTfmRid - .Where((directoryOnEveryLevel, i) => directoryOnEveryLevel != entryPointPathInArray[i]) - .Any(); + return false; } - bool PathInLockFileDirectoriesStartWithToolsAndFollowsTwoSubFolder() + if (pathInLockFilePathInArray[0] != "tools") { - if (pathInLockFilePathInArray.Length - entryPointPathInArray.Length != 3) - { - return false; - } - - if (pathInLockFilePathInArray[0] != "tools") - { - return false; - } - - return true; + return false; } - string[] SplitPathByDirectorySeparator(string path) + return true; + } + + private static string[] SplitPathByDirectorySeparator(string path) + { + if (string.IsNullOrEmpty(path)) { - return path.Split('\\', '/'); + return new string[0]; } + + return path.Split('\\', '/'); } } } diff --git a/src/dotnet/ToolPackage/ToolPackageInstance.cs b/src/dotnet/ToolPackage/ToolPackageInstance.cs index 4dd9733fb..c8be8d5ca 100644 --- a/src/dotnet/ToolPackage/ToolPackageInstance.cs +++ b/src/dotnet/ToolPackage/ToolPackageInstance.cs @@ -7,13 +7,17 @@ using Microsoft.DotNet.Tools; using Microsoft.Extensions.EnvironmentAbstractions; using NuGet.ProjectModel; using NuGet.Versioning; +using Microsoft.DotNet.Cli.Utils; namespace Microsoft.DotNet.ToolPackage { // This is named "ToolPackageInstance" because "ToolPackage" would conflict with the namespace internal class ToolPackageInstance : IToolPackage { + private const string PackagedShimsDirectoryConvention = "shims"; + public IEnumerable Warnings => _toolConfiguration.Value.Warnings; + public PackageId Id { get; private set; } public NuGetVersion Version { get; private set; } @@ -28,12 +32,21 @@ namespace Microsoft.DotNet.ToolPackage } } + public IReadOnlyList PackagedShims + { + get + { + return _packagedShims.Value; + } + } + private const string AssetsFileName = "project.assets.json"; private const string ToolSettingsFileName = "DotnetToolSettings.xml"; private IToolPackageStore _store; private Lazy> _commands; private Lazy _toolConfiguration; + private Lazy> _packagedShims; public ToolPackageInstance( IToolPackageStore store, @@ -43,6 +56,7 @@ namespace Microsoft.DotNet.ToolPackage { _store = store ?? throw new ArgumentNullException(nameof(store)); _commands = new Lazy>(GetCommands); + _packagedShims = new Lazy>(GetPackagedShims); Id = id; Version = version ?? throw new ArgumentNullException(nameof(version)); @@ -107,11 +121,11 @@ namespace Microsoft.DotNet.ToolPackage try { var commands = new List(); - var lockFile = new LockFileFormat().Read(PackageDirectory.WithFile(AssetsFileName).Value); - var library = FindLibraryInLockFile(lockFile); + LockFile lockFile = new LockFileFormat().Read(PackageDirectory.WithFile(AssetsFileName).Value); + LockFileTargetLibrary library = FindLibraryInLockFile(lockFile); ToolConfiguration configuration = _toolConfiguration.Value; - var entryPointFromLockFile = FindItemInTargetLibrary(library, configuration.ToolAssemblyEntryPoint); + LockFileItem entryPointFromLockFile = FindItemInTargetLibrary(library, configuration.ToolAssemblyEntryPoint); if (entryPointFromLockFile == null) { throw new ToolConfigurationException( @@ -125,11 +139,7 @@ namespace Microsoft.DotNet.ToolPackage commands.Add(new CommandSettings( configuration.CommandName, "dotnet", - PackageDirectory - .WithSubDirectories( - Id.ToString(), - library.Version.ToNormalizedString()) - .WithFile(entryPointFromLockFile.Path))); + LockFileRelativePathToFullFilePath(entryPointFromLockFile.Path, library))); return commands; } @@ -143,6 +153,15 @@ namespace Microsoft.DotNet.ToolPackage } } + private FilePath LockFileRelativePathToFullFilePath(string lockFileRelativePath, LockFileTargetLibrary library) + { + return PackageDirectory + .WithSubDirectories( + Id.ToString(), + library.Version.ToNormalizedString()) + .WithFile(lockFileRelativePath); + } + private ToolConfiguration GetToolConfiguration() { try @@ -161,6 +180,55 @@ namespace Microsoft.DotNet.ToolPackage } } + private IReadOnlyList GetPackagedShims() + { + LockFileTargetLibrary library; + try + { + LockFile lockFile = new LockFileFormat().Read(PackageDirectory.WithFile(AssetsFileName).Value); + library = FindLibraryInLockFile(lockFile); + } + catch (Exception ex) when (ex is UnauthorizedAccessException || ex is IOException) + { + throw new ToolPackageException( + string.Format( + CommonLocalizableStrings.FailedToReadNuGetLockFile, + Id, + ex.Message), + ex); + } + + IEnumerable filesUnderShimsDirectory = library + ?.ToolsAssemblies + ?.Where(t => LockFileMatcher.MatchesDirectoryPath(t, PackagedShimsDirectoryConvention)); + + if (filesUnderShimsDirectory == null) + { + return Array.Empty(); + } + + IEnumerable allAvailableShimRuntimeIdentifiers = filesUnderShimsDirectory + .Select(f => f.Path.Split('\\', '/')?[4]) // ex: "tools/netcoreapp2.1/any/shims/osx-x64/demo" osx-x64 is at [4] + .Where(f => !string.IsNullOrEmpty(f)); + + if (new FrameworkDependencyFile().TryGetMostFitRuntimeIdentifier( + DotnetFiles.VersionFileObject.BuildRid, + allAvailableShimRuntimeIdentifiers.ToArray(), + out var mostFitRuntimeIdentifier)) + { + return library + ?.ToolsAssemblies + ?.Where(l => + LockFileMatcher.MatchesDirectoryPath(l, $"{PackagedShimsDirectoryConvention}/{mostFitRuntimeIdentifier}")) + .Select(l => LockFileRelativePathToFullFilePath(l.Path, library)).ToArray() + ?? Array.Empty(); + } + else + { + return Array.Empty(); + } + } + private ToolConfiguration DeserializeToolConfiguration(string ToolSettingsFileName, LockFileTargetLibrary library) { var dotnetToolSettings = FindItemInTargetLibrary(library, ToolSettingsFileName); diff --git a/src/dotnet/commands/dotnet-tool/install/ToolInstallCommand.cs b/src/dotnet/commands/dotnet-tool/install/ToolInstallCommand.cs index 42bcb8790..845644239 100644 --- a/src/dotnet/commands/dotnet-tool/install/ToolInstallCommand.cs +++ b/src/dotnet/commands/dotnet-tool/install/ToolInstallCommand.cs @@ -142,7 +142,7 @@ namespace Microsoft.DotNet.Tools.Tool.Install foreach (var command in package.Commands) { - shellShimRepository.CreateShim(command.Executable, command.Name); + shellShimRepository.CreateShim(command.Executable, command.Name, package.PackagedShims); } scope.Complete(); diff --git a/src/dotnet/xlf/CommonLocalizableStrings.cs.xlf b/src/dotnet/xlf/CommonLocalizableStrings.cs.xlf index aa36da3b1..8e1334b72 100644 --- a/src/dotnet/xlf/CommonLocalizableStrings.cs.xlf +++ b/src/dotnet/xlf/CommonLocalizableStrings.cs.xlf @@ -883,6 +883,16 @@ setx PATH "%PATH%;{0}" Failed to add '{0}' to the PATH environment variable. Please add this directory to your PATH to use tools installed with 'dotnet tool install'. + + More than one packaged shim is available: {0}. + More than one packaged shim is available: {0}. + + + + Failed to read NuGet LockFile for tool package '{0}': {1} + Failed to read NuGet LockFile for tool package '{0}': {1} + + \ No newline at end of file diff --git a/src/dotnet/xlf/CommonLocalizableStrings.de.xlf b/src/dotnet/xlf/CommonLocalizableStrings.de.xlf index c944f6a40..f79fd7412 100644 --- a/src/dotnet/xlf/CommonLocalizableStrings.de.xlf +++ b/src/dotnet/xlf/CommonLocalizableStrings.de.xlf @@ -883,6 +883,16 @@ setx PATH "%PATH%;{0}" Failed to add '{0}' to the PATH environment variable. Please add this directory to your PATH to use tools installed with 'dotnet tool install'. + + More than one packaged shim is available: {0}. + More than one packaged shim is available: {0}. + + + + Failed to read NuGet LockFile for tool package '{0}': {1} + Failed to read NuGet LockFile for tool package '{0}': {1} + + \ No newline at end of file diff --git a/src/dotnet/xlf/CommonLocalizableStrings.es.xlf b/src/dotnet/xlf/CommonLocalizableStrings.es.xlf index 58718eb15..ffb99ee4e 100644 --- a/src/dotnet/xlf/CommonLocalizableStrings.es.xlf +++ b/src/dotnet/xlf/CommonLocalizableStrings.es.xlf @@ -883,6 +883,16 @@ setx PATH "%PATH%;{0}" Failed to add '{0}' to the PATH environment variable. Please add this directory to your PATH to use tools installed with 'dotnet tool install'. + + More than one packaged shim is available: {0}. + More than one packaged shim is available: {0}. + + + + Failed to read NuGet LockFile for tool package '{0}': {1} + Failed to read NuGet LockFile for tool package '{0}': {1} + + \ No newline at end of file diff --git a/src/dotnet/xlf/CommonLocalizableStrings.fr.xlf b/src/dotnet/xlf/CommonLocalizableStrings.fr.xlf index 8b6d109c8..13142465f 100644 --- a/src/dotnet/xlf/CommonLocalizableStrings.fr.xlf +++ b/src/dotnet/xlf/CommonLocalizableStrings.fr.xlf @@ -883,6 +883,16 @@ setx PATH "%PATH%;{0}" Failed to add '{0}' to the PATH environment variable. Please add this directory to your PATH to use tools installed with 'dotnet tool install'. + + More than one packaged shim is available: {0}. + More than one packaged shim is available: {0}. + + + + Failed to read NuGet LockFile for tool package '{0}': {1} + Failed to read NuGet LockFile for tool package '{0}': {1} + + \ No newline at end of file diff --git a/src/dotnet/xlf/CommonLocalizableStrings.it.xlf b/src/dotnet/xlf/CommonLocalizableStrings.it.xlf index 7d37d0324..cf4725a56 100644 --- a/src/dotnet/xlf/CommonLocalizableStrings.it.xlf +++ b/src/dotnet/xlf/CommonLocalizableStrings.it.xlf @@ -883,6 +883,16 @@ setx PATH "%PATH%;{0}" Failed to add '{0}' to the PATH environment variable. Please add this directory to your PATH to use tools installed with 'dotnet tool install'. + + More than one packaged shim is available: {0}. + More than one packaged shim is available: {0}. + + + + Failed to read NuGet LockFile for tool package '{0}': {1} + Failed to read NuGet LockFile for tool package '{0}': {1} + + \ No newline at end of file diff --git a/src/dotnet/xlf/CommonLocalizableStrings.ja.xlf b/src/dotnet/xlf/CommonLocalizableStrings.ja.xlf index f3806381e..25533c941 100644 --- a/src/dotnet/xlf/CommonLocalizableStrings.ja.xlf +++ b/src/dotnet/xlf/CommonLocalizableStrings.ja.xlf @@ -883,6 +883,16 @@ setx PATH "%PATH%;{0}" Failed to add '{0}' to the PATH environment variable. Please add this directory to your PATH to use tools installed with 'dotnet tool install'. + + More than one packaged shim is available: {0}. + More than one packaged shim is available: {0}. + + + + Failed to read NuGet LockFile for tool package '{0}': {1} + Failed to read NuGet LockFile for tool package '{0}': {1} + + \ No newline at end of file diff --git a/src/dotnet/xlf/CommonLocalizableStrings.ko.xlf b/src/dotnet/xlf/CommonLocalizableStrings.ko.xlf index 18e4e1ebc..d6ccc832f 100644 --- a/src/dotnet/xlf/CommonLocalizableStrings.ko.xlf +++ b/src/dotnet/xlf/CommonLocalizableStrings.ko.xlf @@ -883,6 +883,16 @@ setx PATH "%PATH%;{0}" Failed to add '{0}' to the PATH environment variable. Please add this directory to your PATH to use tools installed with 'dotnet tool install'. + + More than one packaged shim is available: {0}. + More than one packaged shim is available: {0}. + + + + Failed to read NuGet LockFile for tool package '{0}': {1} + Failed to read NuGet LockFile for tool package '{0}': {1} + + \ No newline at end of file diff --git a/src/dotnet/xlf/CommonLocalizableStrings.pl.xlf b/src/dotnet/xlf/CommonLocalizableStrings.pl.xlf index c93085baa..1dc4ed34a 100644 --- a/src/dotnet/xlf/CommonLocalizableStrings.pl.xlf +++ b/src/dotnet/xlf/CommonLocalizableStrings.pl.xlf @@ -883,6 +883,16 @@ setx PATH "%PATH%;{0}" Failed to add '{0}' to the PATH environment variable. Please add this directory to your PATH to use tools installed with 'dotnet tool install'. + + More than one packaged shim is available: {0}. + More than one packaged shim is available: {0}. + + + + Failed to read NuGet LockFile for tool package '{0}': {1} + Failed to read NuGet LockFile for tool package '{0}': {1} + + \ No newline at end of file diff --git a/src/dotnet/xlf/CommonLocalizableStrings.pt-BR.xlf b/src/dotnet/xlf/CommonLocalizableStrings.pt-BR.xlf index 64d6bb597..282310d02 100644 --- a/src/dotnet/xlf/CommonLocalizableStrings.pt-BR.xlf +++ b/src/dotnet/xlf/CommonLocalizableStrings.pt-BR.xlf @@ -883,6 +883,16 @@ setx PATH "%PATH%;{0}" Failed to add '{0}' to the PATH environment variable. Please add this directory to your PATH to use tools installed with 'dotnet tool install'. + + More than one packaged shim is available: {0}. + More than one packaged shim is available: {0}. + + + + Failed to read NuGet LockFile for tool package '{0}': {1} + Failed to read NuGet LockFile for tool package '{0}': {1} + + \ No newline at end of file diff --git a/src/dotnet/xlf/CommonLocalizableStrings.ru.xlf b/src/dotnet/xlf/CommonLocalizableStrings.ru.xlf index 84a2a6885..6d8268d94 100644 --- a/src/dotnet/xlf/CommonLocalizableStrings.ru.xlf +++ b/src/dotnet/xlf/CommonLocalizableStrings.ru.xlf @@ -883,6 +883,16 @@ setx PATH "%PATH%;{0}" Failed to add '{0}' to the PATH environment variable. Please add this directory to your PATH to use tools installed with 'dotnet tool install'. + + More than one packaged shim is available: {0}. + More than one packaged shim is available: {0}. + + + + Failed to read NuGet LockFile for tool package '{0}': {1} + Failed to read NuGet LockFile for tool package '{0}': {1} + + \ No newline at end of file diff --git a/src/dotnet/xlf/CommonLocalizableStrings.tr.xlf b/src/dotnet/xlf/CommonLocalizableStrings.tr.xlf index 158df3e77..07e8e75b8 100644 --- a/src/dotnet/xlf/CommonLocalizableStrings.tr.xlf +++ b/src/dotnet/xlf/CommonLocalizableStrings.tr.xlf @@ -883,6 +883,16 @@ setx PATH "%PATH%;{0}" Failed to add '{0}' to the PATH environment variable. Please add this directory to your PATH to use tools installed with 'dotnet tool install'. + + More than one packaged shim is available: {0}. + More than one packaged shim is available: {0}. + + + + Failed to read NuGet LockFile for tool package '{0}': {1} + Failed to read NuGet LockFile for tool package '{0}': {1} + + \ No newline at end of file diff --git a/src/dotnet/xlf/CommonLocalizableStrings.zh-Hans.xlf b/src/dotnet/xlf/CommonLocalizableStrings.zh-Hans.xlf index bb980cac5..3b5ab3369 100644 --- a/src/dotnet/xlf/CommonLocalizableStrings.zh-Hans.xlf +++ b/src/dotnet/xlf/CommonLocalizableStrings.zh-Hans.xlf @@ -883,6 +883,16 @@ setx PATH "%PATH%;{0}" Failed to add '{0}' to the PATH environment variable. Please add this directory to your PATH to use tools installed with 'dotnet tool install'. + + More than one packaged shim is available: {0}. + More than one packaged shim is available: {0}. + + + + Failed to read NuGet LockFile for tool package '{0}': {1} + Failed to read NuGet LockFile for tool package '{0}': {1} + + \ No newline at end of file diff --git a/src/dotnet/xlf/CommonLocalizableStrings.zh-Hant.xlf b/src/dotnet/xlf/CommonLocalizableStrings.zh-Hant.xlf index a8751bbee..4b1a80094 100644 --- a/src/dotnet/xlf/CommonLocalizableStrings.zh-Hant.xlf +++ b/src/dotnet/xlf/CommonLocalizableStrings.zh-Hant.xlf @@ -883,6 +883,16 @@ setx PATH "%PATH%;{0}" Failed to add '{0}' to the PATH environment variable. Please add this directory to your PATH to use tools installed with 'dotnet tool install'. + + More than one packaged shim is available: {0}. + More than one packaged shim is available: {0}. + + + + Failed to read NuGet LockFile for tool package '{0}': {1} + Failed to read NuGet LockFile for tool package '{0}': {1} + + \ No newline at end of file diff --git a/test/Microsoft.DotNet.Configurer.UnitTests/GivenAFunctionReturnStringAndFakeFileSystem.cs b/test/Microsoft.DotNet.Configurer.UnitTests/GivenAFunctionReturnStringAndFakeFileSystem.cs index 3844e07c1..7f9645ce0 100644 --- a/test/Microsoft.DotNet.Configurer.UnitTests/GivenAFunctionReturnStringAndFakeFileSystem.cs +++ b/test/Microsoft.DotNet.Configurer.UnitTests/GivenAFunctionReturnStringAndFakeFileSystem.cs @@ -134,6 +134,11 @@ namespace Microsoft.DotNet.Configurer.UnitTests { throw new UnauthorizedAccessException(); } + + public void Copy(string source, string destination) + { + throw new UnauthorizedAccessException(); + } } private class NoPermissionDirectoryFake : IDirectory diff --git a/test/Microsoft.DotNet.Configurer.UnitTests/GivenANuGetCacheSentinel.cs b/test/Microsoft.DotNet.Configurer.UnitTests/GivenANuGetCacheSentinel.cs index c224254e0..2113d9d17 100644 --- a/test/Microsoft.DotNet.Configurer.UnitTests/GivenANuGetCacheSentinel.cs +++ b/test/Microsoft.DotNet.Configurer.UnitTests/GivenANuGetCacheSentinel.cs @@ -286,6 +286,11 @@ namespace Microsoft.DotNet.Configurer.UnitTests { throw new NotImplementedException(); } + + public void Copy(string source, string destination) + { + throw new NotImplementedException(); + } } private class MockStream : MemoryStream diff --git a/test/Microsoft.DotNet.ShellShim.Tests/ShellShimRepositoryTests.cs b/test/Microsoft.DotNet.ShellShim.Tests/ShellShimRepositoryTests.cs index 15b9d4f7c..90a3bb83d 100644 --- a/test/Microsoft.DotNet.ShellShim.Tests/ShellShimRepositoryTests.cs +++ b/test/Microsoft.DotNet.ShellShim.Tests/ShellShimRepositoryTests.cs @@ -119,7 +119,7 @@ namespace Microsoft.DotNet.ShellShim.Tests IShellShimRepository shellShimRepository; if (testMockBehaviorIsInSync) { - shellShimRepository = new ShellShimRepositoryMock(new DirectoryPath(pathToShim)); + shellShimRepository = GetShellShimRepositoryWithMockMaker(pathToShim); } else { @@ -161,7 +161,7 @@ namespace Microsoft.DotNet.ShellShim.Tests IShellShimRepository shellShimRepository; if (testMockBehaviorIsInSync) { - shellShimRepository = new ShellShimRepositoryMock(new DirectoryPath(pathToShim)); + shellShimRepository = GetShellShimRepositoryWithMockMaker(pathToShim); } else { @@ -198,7 +198,7 @@ namespace Microsoft.DotNet.ShellShim.Tests IShellShimRepository shellShimRepository; if (testMockBehaviorIsInSync) { - shellShimRepository = new ShellShimRepositoryMock(new DirectoryPath(pathToShim)); + shellShimRepository = GetShellShimRepositoryWithMockMaker(pathToShim); } else { @@ -223,7 +223,7 @@ namespace Microsoft.DotNet.ShellShim.Tests IShellShimRepository shellShimRepository; if (testMockBehaviorIsInSync) { - shellShimRepository = new ShellShimRepositoryMock(new DirectoryPath(pathToShim)); + shellShimRepository = GetShellShimRepositoryWithMockMaker(pathToShim); } else { @@ -252,7 +252,7 @@ namespace Microsoft.DotNet.ShellShim.Tests IShellShimRepository shellShimRepository; if (testMockBehaviorIsInSync) { - shellShimRepository = new ShellShimRepositoryMock(new DirectoryPath(pathToShim)); + shellShimRepository = GetShellShimRepositoryWithMockMaker(pathToShim); } else { @@ -288,7 +288,7 @@ namespace Microsoft.DotNet.ShellShim.Tests IShellShimRepository shellShimRepository; if (testMockBehaviorIsInSync) { - shellShimRepository = new ShellShimRepositoryMock(new DirectoryPath(pathToShim)); + shellShimRepository = GetShellShimRepositoryWithMockMaker(pathToShim); } else { @@ -315,6 +315,67 @@ namespace Microsoft.DotNet.ShellShim.Tests Directory.EnumerateFileSystemEntries(pathToShim).Should().BeEmpty(); } + [Fact] + public void WhenPackagedShimProvidedItCopies() + { + const string tokenToIdentifyCopiedShim = "packagedShim"; + + var shellCommandName = nameof(ShellShimRepositoryTests) + Path.GetRandomFileName(); + var pathToShim = GetNewCleanFolderUnderTempRoot(); + var packagedShimFolder = GetNewCleanFolderUnderTempRoot(); + var dummyShimPath = Path.Combine(packagedShimFolder, shellCommandName); + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + dummyShimPath = dummyShimPath + ".exe"; + } + + File.WriteAllText(dummyShimPath, tokenToIdentifyCopiedShim); + + ShellShimRepository shellShimRepository = GetShellShimRepositoryWithMockMaker(pathToShim); + + shellShimRepository.CreateShim( + new FilePath("dummy.dll"), + shellCommandName, + new[] {new FilePath(dummyShimPath)}); + + var createdShim = Directory.EnumerateFileSystemEntries(pathToShim).Single(); + File.ReadAllText(createdShim).Should().Contain(tokenToIdentifyCopiedShim); + } + + [Fact] + public void WhenMutipleSameNamePackagedShimProvidedItThrows() + { + const string tokenToIdentifyCopiedShim = "packagedShim"; + + var shellCommandName = nameof(ShellShimRepositoryTests) + Path.GetRandomFileName(); + var pathToShim = GetNewCleanFolderUnderTempRoot(); + var packagedShimFolder = GetNewCleanFolderUnderTempRoot(); + var dummyShimPath = Path.Combine(packagedShimFolder, shellCommandName); + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + dummyShimPath = dummyShimPath + ".exe"; + } + + File.WriteAllText(dummyShimPath, tokenToIdentifyCopiedShim); + ShellShimRepository shellShimRepository = GetShellShimRepositoryWithMockMaker(pathToShim); + + FilePath[] filePaths = new[] { new FilePath(dummyShimPath), new FilePath("path" + dummyShimPath) }; + + Action a = () => shellShimRepository.CreateShim( + new FilePath("dummy.dll"), + shellCommandName, + new[] { new FilePath(dummyShimPath), new FilePath("path" + dummyShimPath) }); + + a.ShouldThrow() + .And.Message + .Should().Contain( + string.Format( + CommonLocalizableStrings.MoreThanOnePackagedShimAvailable, + string.Join(';', filePaths))); + } + private static void MakeNameConflictingCommand(string pathToPlaceShim, string shellCommandName) { if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) @@ -440,5 +501,12 @@ namespace Microsoft.DotNet.ShellShim.Tests return CleanFolderUnderTempRoot.FullName; } + + private ShellShimRepository GetShellShimRepositoryWithMockMaker(string pathToShim) + { + return new ShellShimRepository( + new DirectoryPath(pathToShim), + appHostShellShimMaker: new AppHostShellShimMakerMock()); + } } } diff --git a/test/Microsoft.DotNet.ToolPackage.Tests/LockFileMatcherTests.cs b/test/Microsoft.DotNet.ToolPackage.Tests/LockFileMatcherTests.cs index 0a0a0cb03..dd658842f 100644 --- a/test/Microsoft.DotNet.ToolPackage.Tests/LockFileMatcherTests.cs +++ b/test/Microsoft.DotNet.ToolPackage.Tests/LockFileMatcherTests.cs @@ -10,7 +10,6 @@ namespace Microsoft.DotNet.ToolPackage.Tests { public class LockFileMatcherTests : TestBase { - [Theory] [InlineData("tools/netcoreapp1.1/any/tool.dll", "tool.dll", true)] [InlineData(@"tools\netcoreapp1.1\any\subDirectory\tool.dll", "subDirectory/tool.dll", true)] @@ -24,5 +23,20 @@ namespace Microsoft.DotNet.ToolPackage.Tests LockFileMatcher.MatchesFile(new LockFileItem(pathInLockFileItem), targetRelativeFilePath) .Should().Be(shouldMatch); } + + + [Theory] + [InlineData("tools/netcoreapp1.1/any/tool.dll", "", true)] + [InlineData(@"tools\netcoreapp1.1\any\subDirectory\tool.dll", "subDirectory", true)] + [InlineData(@"tools\netcoreapp1.1\any\subDirectory\tool.dll", "sub", false)] + [InlineData("tools/netcoreapp1.1/any/subDirectory/tool.dll", "any/subDirectory", false)] + public void MatchesDirectoryPathTests( + string pathInLockFileItem, + string targetRelativeFilePath, + bool shouldMatch) + { + LockFileMatcher.MatchesDirectoryPath(new LockFileItem(pathInLockFileItem), targetRelativeFilePath) + .Should().Be(shouldMatch); + } } } diff --git a/test/Microsoft.DotNet.ToolPackage.Tests/Microsoft.DotNet.ToolPackage.Tests.csproj b/test/Microsoft.DotNet.ToolPackage.Tests/Microsoft.DotNet.ToolPackage.Tests.csproj index 27ff23b4b..ad85df299 100644 --- a/test/Microsoft.DotNet.ToolPackage.Tests/Microsoft.DotNet.ToolPackage.Tests.csproj +++ b/test/Microsoft.DotNet.ToolPackage.Tests/Microsoft.DotNet.ToolPackage.Tests.csproj @@ -45,6 +45,11 @@ + + + + + @@ -59,4 +64,17 @@ + + + + $(BaseOutputPath)/TestAsset/SampleGlobalToolWithShim + + + + + + + + + diff --git a/test/Microsoft.DotNet.ToolPackage.Tests/SampleGlobalToolWithShim/DotnetToolSettings.xml b/test/Microsoft.DotNet.ToolPackage.Tests/SampleGlobalToolWithShim/DotnetToolSettings.xml new file mode 100644 index 000000000..92142c78d --- /dev/null +++ b/test/Microsoft.DotNet.ToolPackage.Tests/SampleGlobalToolWithShim/DotnetToolSettings.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/test/Microsoft.DotNet.ToolPackage.Tests/SampleGlobalToolWithShim/Program.cs b/test/Microsoft.DotNet.ToolPackage.Tests/SampleGlobalToolWithShim/Program.cs new file mode 100644 index 000000000..1140102df --- /dev/null +++ b/test/Microsoft.DotNet.ToolPackage.Tests/SampleGlobalToolWithShim/Program.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; + +namespace consoledemo +{ + class Program + { + static void Main(string[] args) + { + var greeting = "Hello World from Global Tool"; + Console.WriteLine(greeting); + } + } +} diff --git a/test/Microsoft.DotNet.ToolPackage.Tests/SampleGlobalToolWithShim/consoledemo.csproj b/test/Microsoft.DotNet.ToolPackage.Tests/SampleGlobalToolWithShim/consoledemo.csproj new file mode 100644 index 000000000..4e16cac58 --- /dev/null +++ b/test/Microsoft.DotNet.ToolPackage.Tests/SampleGlobalToolWithShim/consoledemo.csproj @@ -0,0 +1,7 @@ + + + Exe + netcoreapp2.1 + consoledemo + + \ No newline at end of file diff --git a/test/Microsoft.DotNet.ToolPackage.Tests/SampleGlobalToolWithShim/dummyshim b/test/Microsoft.DotNet.ToolPackage.Tests/SampleGlobalToolWithShim/dummyshim new file mode 100644 index 000000000..8ba74aee5 --- /dev/null +++ b/test/Microsoft.DotNet.ToolPackage.Tests/SampleGlobalToolWithShim/dummyshim @@ -0,0 +1 @@ +packagedshim \ No newline at end of file diff --git a/test/Microsoft.DotNet.ToolPackage.Tests/SampleGlobalToolWithShim/dummyshim.exe b/test/Microsoft.DotNet.ToolPackage.Tests/SampleGlobalToolWithShim/dummyshim.exe new file mode 100644 index 000000000..8ba74aee5 --- /dev/null +++ b/test/Microsoft.DotNet.ToolPackage.Tests/SampleGlobalToolWithShim/dummyshim.exe @@ -0,0 +1 @@ +packagedshim \ No newline at end of file diff --git a/test/Microsoft.DotNet.ToolPackage.Tests/SampleGlobalToolWithShim/includepublish.nuspec b/test/Microsoft.DotNet.ToolPackage.Tests/SampleGlobalToolWithShim/includepublish.nuspec new file mode 100644 index 000000000..b2ed9d349 --- /dev/null +++ b/test/Microsoft.DotNet.ToolPackage.Tests/SampleGlobalToolWithShim/includepublish.nuspec @@ -0,0 +1,20 @@ + + + + global.tool.console.demo.with.shim + 1.0.4 + test app + testauthor + + + + + + + + + + + + + diff --git a/test/Microsoft.DotNet.ToolPackage.Tests/ToolPackageInstanceTests.cs b/test/Microsoft.DotNet.ToolPackage.Tests/ToolPackageInstanceTests.cs new file mode 100644 index 000000000..3c035e17f --- /dev/null +++ b/test/Microsoft.DotNet.ToolPackage.Tests/ToolPackageInstanceTests.cs @@ -0,0 +1,130 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Transactions; +using FluentAssertions; +using Microsoft.DotNet.Tools.Test.Utilities; +using Microsoft.DotNet.Cli; +using Microsoft.DotNet.Cli.Utils; +using Microsoft.DotNet.Tools; +using Microsoft.DotNet.Tools.Tool.Install; +using Microsoft.DotNet.Tools.Tests.ComponentMocks; +using Microsoft.Extensions.DependencyModel.Tests; +using Microsoft.Extensions.EnvironmentAbstractions; +using NuGet.Versioning; +using Xunit; + +namespace Microsoft.DotNet.ToolPackage.Tests +{ + public class ToolPackageInstanceTests : TestBase + { + [Theory] + [InlineData(false)] + [InlineData(true)] + public void GivenAnInstalledPackageUninstallRemovesThePackage(bool testMockBehaviorIsInSync) + { + var source = GetTestLocalFeedPath(); + + var (store, installer, reporter, fileSystem) = Setup( + useMock: testMockBehaviorIsInSync, + feeds: GetMockFeedsForSource(source)); + + var package = installer.InstallPackage( + packageId: TestPackageId, + versionRange: VersionRange.Parse(TestPackageVersion), + targetFramework: _testTargetframework, + additionalFeeds: new[] {source}); + + package.PackagedShims.Should().ContainSingle(f => f.Value.Contains("demo.exe") || f.Value.Contains("demo")); + + package.Uninstall(); + } + + private static FilePath GetUniqueTempProjectPathEachTest() + { + var tempProjectDirectory = + new DirectoryPath(Path.GetTempPath()).WithSubDirectories(Path.GetRandomFileName()); + var tempProjectPath = + tempProjectDirectory.WithFile(Path.GetRandomFileName() + ".csproj"); + return tempProjectPath; + } + + private static IEnumerable GetMockFeedsForSource(string source) + { + return new[] + { + new MockFeed + { + Type = MockFeedType.ImplicitAdditionalFeed, + Uri = source, + Packages = new List + { + new MockFeedPackage + { + PackageId = TestPackageId.ToString(), + Version = TestPackageVersion + } + } + } + }; + } + + private static (IToolPackageStore, IToolPackageInstaller, BufferedReporter, IFileSystem) Setup( + bool useMock, + IEnumerable feeds = null, + FilePath? tempProject = null, + DirectoryPath? offlineFeed = null) + { + var root = new DirectoryPath(Path.Combine(TempRoot.Root, Path.GetRandomFileName())); + var reporter = new BufferedReporter(); + + IFileSystem fileSystem; + IToolPackageStore store; + IToolPackageInstaller installer; + if (useMock) + { + var packagedShimsMap = new Dictionary> + { + [TestPackageId] = new FilePath[] {new FilePath("path/demo.exe")} + }; + + fileSystem = new FileSystemMockBuilder().Build(); + store = new ToolPackageStoreMock(root, fileSystem); + installer = new ToolPackageInstallerMock( + fileSystem: fileSystem, + store: store, + projectRestorer: new ProjectRestorerMock( + fileSystem: fileSystem, + reporter: reporter, + feeds: feeds), + packagedShimsMap: packagedShimsMap); + } + else + { + fileSystem = new FileSystemWrapper(); + store = new ToolPackageStore(root); + installer = new ToolPackageInstaller( + store: store, + projectRestorer: new ProjectRestorer(reporter), + tempProject: tempProject ?? GetUniqueTempProjectPathEachTest(), + offlineFeed: offlineFeed ?? new DirectoryPath("does not exist")); + } + + store.Root.Value.Should().Be(Path.GetFullPath(root.Value)); + + return (store, installer, reporter, fileSystem); + } + + private static string GetTestLocalFeedPath() => + Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "TestAssetLocalNugetFeed"); + + private readonly string _testTargetframework = BundledTargetFramework.GetTargetFrameworkMoniker(); + private const string TestPackageVersion = "1.0.4"; + private static readonly PackageId TestPackageId = new PackageId("global.tool.console.demo.with.shim"); + } +} diff --git a/test/Microsoft.DotNet.Tools.Tests.ComponentMocks/AppHostShellShimMakerMock.cs b/test/Microsoft.DotNet.Tools.Tests.ComponentMocks/AppHostShellShimMakerMock.cs new file mode 100644 index 000000000..1dca12162 --- /dev/null +++ b/test/Microsoft.DotNet.Tools.Tests.ComponentMocks/AppHostShellShimMakerMock.cs @@ -0,0 +1,38 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Microsoft.DotNet.ShellShim; +using Microsoft.Extensions.EnvironmentAbstractions; +using Newtonsoft.Json; + +namespace Microsoft.DotNet.Tools.Tests.ComponentMocks +{ + internal class AppHostShellShimMakerMock : IAppHostShellShimMaker + { + private static IFileSystem _fileSystem; + + public AppHostShellShimMakerMock(IFileSystem fileSystem = null) + { + _fileSystem = fileSystem ?? new FileSystemWrapper(); + } + + public void CreateApphostShellShim(FilePath entryPoint, FilePath shimPath) + { + var shim = new FakeShim + { + Runner = "dotnet", + ExecutablePath = entryPoint.Value + }; + + _fileSystem.File.WriteAllText( + shimPath.Value, + JsonConvert.SerializeObject(shim)); + } + + public class FakeShim + { + public string Runner { get; set; } + public string ExecutablePath { get; set; } + } + } +} diff --git a/test/Microsoft.DotNet.Tools.Tests.ComponentMocks/ShellShimRepositoryMock.cs b/test/Microsoft.DotNet.Tools.Tests.ComponentMocks/ShellShimRepositoryMock.cs deleted file mode 100644 index d67167d0e..000000000 --- a/test/Microsoft.DotNet.Tools.Tests.ComponentMocks/ShellShimRepositoryMock.cs +++ /dev/null @@ -1,104 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using System; -using System.Collections.Generic; -using System.IO; -using System.Runtime.InteropServices; -using Microsoft.DotNet.Cli; -using Microsoft.DotNet.Cli.Utils; -using Microsoft.DotNet.ShellShim; -using Microsoft.Extensions.EnvironmentAbstractions; -using Newtonsoft.Json; - -namespace Microsoft.DotNet.Tools.Tests.ComponentMocks -{ - internal class ShellShimRepositoryMock : IShellShimRepository - { - private static IFileSystem _fileSystem; - private readonly DirectoryPath _pathToPlaceShim; - - public ShellShimRepositoryMock(DirectoryPath pathToPlaceShim, IFileSystem fileSystem = null) - { - _pathToPlaceShim = pathToPlaceShim; - _fileSystem = fileSystem ?? new FileSystemWrapper(); - } - - public void CreateShim(FilePath targetExecutablePath, string commandName) - { - if (ShimExists(commandName)) - { - throw new ShellShimException( - string.Format(CommonLocalizableStrings.ShellShimConflict, - commandName)); - } - - TransactionalAction.Run( - action: () => { - var shim = new FakeShim - { - Runner = "dotnet", - ExecutablePath = targetExecutablePath.Value - }; - - _fileSystem.File.WriteAllText( - GetShimPath(commandName).Value, - JsonConvert.SerializeObject(shim)); - }, - rollback: () => { - _fileSystem.File.Delete(GetShimPath(commandName).Value); - }); - } - - public void RemoveShim(string commandName) - { - var originalShimPath = GetShimPath(commandName); - if (!_fileSystem.File.Exists(originalShimPath.Value)) - { - return; - } - - string tempShimPath = null; - TransactionalAction.Run( - action: () => { - var tempFile = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); - _fileSystem.File.Move(originalShimPath.Value, tempFile); - tempShimPath = tempFile; - }, - commit: () => { - if (tempShimPath != null) - { - _fileSystem.File.Delete(tempShimPath); - } - }, - rollback: () => { - if (tempShimPath != null) - { - _fileSystem.File.Move(tempShimPath, originalShimPath.Value); - } - }); - } - - private bool ShimExists(string commandName) - { - return _fileSystem.File.Exists(GetShimPath(commandName).Value); - } - - private FilePath GetShimPath(string shellCommandName) - { - var shimPath = Path.Combine(_pathToPlaceShim.Value, shellCommandName); - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - shimPath += ".exe"; - } - - return new FilePath(shimPath); - } - - public class FakeShim - { - public string Runner { get; set; } - public string ExecutablePath { get; set; } - } - } -} diff --git a/test/Microsoft.DotNet.Tools.Tests.ComponentMocks/ToolPackageInstallerMock.cs b/test/Microsoft.DotNet.Tools.Tests.ComponentMocks/ToolPackageInstallerMock.cs index facc85132..9737ff858 100644 --- a/test/Microsoft.DotNet.Tools.Tests.ComponentMocks/ToolPackageInstallerMock.cs +++ b/test/Microsoft.DotNet.Tools.Tests.ComponentMocks/ToolPackageInstallerMock.cs @@ -23,19 +23,22 @@ namespace Microsoft.DotNet.Tools.Tests.ComponentMocks private readonly IFileSystem _fileSystem; private readonly Action _installCallback; private readonly Dictionary> _warningsMap; + private readonly Dictionary> _packagedShimsMap; public ToolPackageInstallerMock( IFileSystem fileSystem, IToolPackageStore store, IProjectRestorer projectRestorer, Action installCallback = null, - Dictionary> warningsMap = null) + Dictionary> warningsMap = null, + Dictionary> packagedShimsMap = null) { _fileSystem = fileSystem ?? throw new ArgumentNullException(nameof(fileSystem)); _store = store ?? throw new ArgumentNullException(nameof(store)); _projectRestorer = projectRestorer ?? throw new ArgumentNullException(nameof(projectRestorer)); _installCallback = installCallback; _warningsMap = warningsMap ?? new Dictionary>(); + _packagedShimsMap = packagedShimsMap ?? new Dictionary>(); } public IToolPackage InstallPackage(PackageId packageId, @@ -92,7 +95,10 @@ namespace Microsoft.DotNet.Tools.Tests.ComponentMocks IEnumerable warnings = null; _warningsMap.TryGetValue(packageId, out warnings); - return new ToolPackageMock(_fileSystem, packageId, version, packageDirectory, warnings: warnings); + IReadOnlyList packedShims = null; + _packagedShimsMap.TryGetValue(packageId, out packedShims); + + return new ToolPackageMock(_fileSystem, packageId, version, packageDirectory, warnings: warnings, packagedShims: packedShims); }, rollback: () => { if (rollbackDirectory != null && _fileSystem.Directory.Exists(rollbackDirectory)) diff --git a/test/Microsoft.DotNet.Tools.Tests.ComponentMocks/ToolPackageMock.cs b/test/Microsoft.DotNet.Tools.Tests.ComponentMocks/ToolPackageMock.cs index 565e140e4..43d430b08 100644 --- a/test/Microsoft.DotNet.Tools.Tests.ComponentMocks/ToolPackageMock.cs +++ b/test/Microsoft.DotNet.Tools.Tests.ComponentMocks/ToolPackageMock.cs @@ -18,6 +18,7 @@ namespace Microsoft.DotNet.Tools.Tests.ComponentMocks private Lazy> _commands; private Action _uninstallCallback; private IEnumerable _warnings; + private readonly IReadOnlyList _packagedShims; public ToolPackageMock( IFileSystem fileSystem, @@ -25,7 +26,8 @@ namespace Microsoft.DotNet.Tools.Tests.ComponentMocks NuGetVersion version, DirectoryPath packageDirectory, Action uninstallCallback = null, - IEnumerable warnings = null) + IEnumerable warnings = null, + IReadOnlyList packagedShims = null) { _fileSystem = fileSystem ?? throw new ArgumentNullException(nameof(fileSystem)); Id = id; @@ -34,6 +36,7 @@ namespace Microsoft.DotNet.Tools.Tests.ComponentMocks _commands = new Lazy>(GetCommands); _uninstallCallback = uninstallCallback; _warnings = warnings ?? new List(); + _packagedShims = packagedShims ?? new List(); } public PackageId Id { get; private set; } @@ -52,6 +55,14 @@ namespace Microsoft.DotNet.Tools.Tests.ComponentMocks public IEnumerable Warnings => _warnings; + public IReadOnlyList PackagedShims + { + get + { + return _packagedShims; + } + } + public void Uninstall() { var rootDirectory = PackageDirectory.GetParentPath(); diff --git a/test/Microsoft.DotNet.Tools.Tests.Utilities/Mock/FileSystemMockBuilder.cs b/test/Microsoft.DotNet.Tools.Tests.Utilities/Mock/FileSystemMockBuilder.cs index 6adce5ca9..f75d3471b 100644 --- a/test/Microsoft.DotNet.Tools.Tests.Utilities/Mock/FileSystemMockBuilder.cs +++ b/test/Microsoft.DotNet.Tools.Tests.Utilities/Mock/FileSystemMockBuilder.cs @@ -132,6 +132,20 @@ namespace Microsoft.Extensions.DependencyModel.Tests _files.Remove(path); } + + public void Copy(string source, string destination) + { + if (!Exists(source)) + { + throw new FileNotFoundException("source does not exist."); + } + if (Exists(destination)) + { + throw new IOException("destination exists."); + } + + _files[destination] = _files[source]; + } } private class DirectoryMock : IDirectory diff --git a/test/dotnet.Tests/CommandTests/ToolInstallCommandTests.cs b/test/dotnet.Tests/CommandTests/ToolInstallCommandTests.cs index 05cca5f33..6ba8a385f 100644 --- a/test/dotnet.Tests/CommandTests/ToolInstallCommandTests.cs +++ b/test/dotnet.Tests/CommandTests/ToolInstallCommandTests.cs @@ -46,7 +46,11 @@ namespace Microsoft.DotNet.Tests.Commands _fileSystem = new FileSystemMockBuilder().Build(); _toolPackageStore = new ToolPackageStoreMock(new DirectoryPath(PathToPlacePackages), _fileSystem); _createShellShimRepository = - (nonGlobalLocation) => new ShellShimRepositoryMock(new DirectoryPath(PathToPlaceShim), _fileSystem); + (nonGlobalLocation) => new ShellShimRepository( + new DirectoryPath(PathToPlaceShim), + fileSystem: _fileSystem, + appHostShellShimMaker: new AppHostShellShimMakerMock(_fileSystem), + filePermissionSetter: new NoOpFilePermissionSetter()); _environmentPathInstructionMock = new EnvironmentPathInstructionMock(_reporter, PathToPlaceShim); _createToolPackageStoreAndInstaller = (_) => (_toolPackageStore, CreateToolPackageInstaller()); @@ -71,7 +75,7 @@ namespace Microsoft.DotNet.Tests.Commands // It is hard to simulate shell behavior. Only Assert shim can point to executable dll _fileSystem.File.Exists(ExpectedCommandPath()).Should().BeTrue(); - var deserializedFakeShim = JsonConvert.DeserializeObject( + var deserializedFakeShim = JsonConvert.DeserializeObject( _fileSystem.File.ReadAllText(ExpectedCommandPath())); _fileSystem.File.Exists(deserializedFakeShim.ExecutablePath).Should().BeTrue(); @@ -117,7 +121,7 @@ namespace Microsoft.DotNet.Tests.Commands _fileSystem.File.Exists(ExpectedCommandPath()) .Should().BeTrue(); var deserializedFakeShim = - JsonConvert.DeserializeObject( + JsonConvert.DeserializeObject( _fileSystem.File.ReadAllText(ExpectedCommandPath())); _fileSystem.File.Exists(deserializedFakeShim.ExecutablePath).Should().BeTrue(); } @@ -455,6 +459,42 @@ namespace Microsoft.DotNet.Tests.Commands _reporter.Lines.Should().NotContain(l => l.Contains(EnvironmentPathInstructionMock.MockInstructionText)); } + [Fact] + public void AndPackagedShimIsProvidedWhenRunWithPackageIdItCreateShimUsingPackagedShim() + { + var extension = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? ".exe" : string.Empty; + var prepackagedShimPath = "packagedShimDirectory/" + ProjectRestorerMock.FakeCommandName + extension; + var tokenToIdentifyPackagedShim = "packagedShim"; + _fileSystem.File.WriteAllText(prepackagedShimPath, tokenToIdentifyPackagedShim); + + var result = Parser.Instance.Parse($"dotnet tool install --tool-path /tmp/folder {PackageId}"); + var appliedCommand = result["dotnet"]["tool"]["install"]; + var parser = Parser.Instance; + var parseResult = parser.ParseFrom("dotnet tool", new[] {"install", "-g", PackageId}); + + var packagedShimsMap = new Dictionary> + { + [new PackageId(PackageId)] = new[] {new FilePath(prepackagedShimPath)} + }; + + var installCommand = new ToolInstallCommand(appliedCommand, + parseResult, + (_) => (_toolPackageStore, new ToolPackageInstallerMock( + fileSystem: _fileSystem, + store: _toolPackageStore, + packagedShimsMap: packagedShimsMap, + projectRestorer: new ProjectRestorerMock( + fileSystem: _fileSystem, + reporter: _reporter))), + _createShellShimRepository, + new EnvironmentPathInstructionMock(_reporter, PathToPlaceShim), + _reporter); + + installCommand.Execute().Should().Be(0); + + _fileSystem.File.ReadAllText(ExpectedCommandPath()).Should().Be(tokenToIdentifyPackagedShim); + } + private IToolPackageInstaller CreateToolPackageInstaller( IEnumerable feeds = null, Action installCallback = null) @@ -476,5 +516,12 @@ namespace Microsoft.DotNet.Tests.Commands "pathToPlace", ProjectRestorerMock.FakeCommandName + extension); } + + private class NoOpFilePermissionSetter : IFilePermissionSetter + { + public void SetUserExecutionPermission(string path) + { + } + } } } diff --git a/test/dotnet.Tests/CommandTests/ToolUninstallCommandTests.cs b/test/dotnet.Tests/CommandTests/ToolUninstallCommandTests.cs index 2e47036ac..902d4eb1d 100644 --- a/test/dotnet.Tests/CommandTests/ToolUninstallCommandTests.cs +++ b/test/dotnet.Tests/CommandTests/ToolUninstallCommandTests.cs @@ -22,6 +22,7 @@ using Xunit; using Parser = Microsoft.DotNet.Cli.Parser; using LocalizableStrings = Microsoft.DotNet.Tools.Tool.Uninstall.LocalizableStrings; using InstallLocalizableStrings = Microsoft.DotNet.Tools.Tool.Install.LocalizableStrings; +using Microsoft.DotNet.ShellShim; namespace Microsoft.DotNet.Tests.Commands { @@ -203,7 +204,10 @@ namespace Microsoft.DotNet.Tests.Commands result["dotnet"]["tool"]["install"], result, (_) => (store, packageInstallerMock), - (_) => new ShellShimRepositoryMock(new DirectoryPath(ShimsDirectory), _fileSystem), + (_) => new ShellShimRepository( + new DirectoryPath(ShimsDirectory), + fileSystem: _fileSystem, + appHostShellShimMaker: new AppHostShellShimMakerMock(_fileSystem)), _environmentPathInstructionMock, _reporter); } @@ -219,7 +223,10 @@ namespace Microsoft.DotNet.Tests.Commands new DirectoryPath(ToolsDirectory), _fileSystem, uninstallCallback), - (_) => new ShellShimRepositoryMock(new DirectoryPath(ShimsDirectory), _fileSystem), + (_) => new ShellShimRepository( + new DirectoryPath(ShimsDirectory), + fileSystem: _fileSystem, + appHostShellShimMaker: new AppHostShellShimMakerMock(_fileSystem)), _reporter); } } diff --git a/test/dotnet.Tests/CommandTests/ToolUpdateCommandTests.cs b/test/dotnet.Tests/CommandTests/ToolUpdateCommandTests.cs index 4be734335..32a64274f 100644 --- a/test/dotnet.Tests/CommandTests/ToolUpdateCommandTests.cs +++ b/test/dotnet.Tests/CommandTests/ToolUpdateCommandTests.cs @@ -17,6 +17,7 @@ using Microsoft.Extensions.EnvironmentAbstractions; using Xunit; using Parser = Microsoft.DotNet.Cli.Parser; using LocalizableStrings = Microsoft.DotNet.Tools.Tool.Update.LocalizableStrings; +using Microsoft.DotNet.ShellShim; namespace Microsoft.DotNet.Tests.Commands { @@ -139,7 +140,7 @@ namespace Microsoft.DotNet.Tests.Commands _mockFeeds ), installCallback: () => throw new ToolConfigurationException("Simulated error"))), - _ => new ShellShimRepositoryMock(new DirectoryPath(ShimsDirectory), _fileSystem), + _ => GetMockedShellShimRepository(), _reporter); Action a = () => command.Execute(); @@ -168,7 +169,7 @@ namespace Microsoft.DotNet.Tests.Commands _mockFeeds ), installCallback: () => throw new ToolConfigurationException("Simulated error"))), - _ => new ShellShimRepositoryMock(new DirectoryPath(ShimsDirectory), _fileSystem), + _ => GetMockedShellShimRepository(), _reporter); Action a = () => command.Execute(); @@ -216,7 +217,7 @@ namespace Microsoft.DotNet.Tests.Commands _reporter, _mockFeeds ))), - (_) => new ShellShimRepositoryMock(new DirectoryPath(ShimsDirectory), _fileSystem), + (_) => GetMockedShellShimRepository(), _environmentPathInstructionMock, _reporter); } @@ -236,8 +237,16 @@ namespace Microsoft.DotNet.Tests.Commands _reporter, _mockFeeds ))), - (_) => new ShellShimRepositoryMock(new DirectoryPath(ShimsDirectory), _fileSystem), + (_) => GetMockedShellShimRepository(), _reporter); } + + private ShellShimRepository GetMockedShellShimRepository() + { + return new ShellShimRepository( + new DirectoryPath(ShimsDirectory), + fileSystem: _fileSystem, + appHostShellShimMaker: new AppHostShellShimMakerMock(_fileSystem)); + } } }