From 2fdf3bc81f9ecac67a3e9a8e2c375e77066a1bb0 Mon Sep 17 00:00:00 2001 From: Rob Reynolds Date: Tue, 12 May 2015 07:49:27 -0500 Subject: [PATCH] (GH-121) Remove unchanged files on uninstall When the files that were installed during install are unchanged, they should be removed from the package directory. Any files that have changed should stick around. --- src/chocolatey.tests/chocolatey.tests.csproj | 1 + .../services/NugetServiceSpecs.cs | 166 ++++++++++++++++++ .../services/FilesService.cs | 12 +- .../services/IFilesService.cs | 7 + .../services/NugetService.cs | 24 ++- 5 files changed, 206 insertions(+), 4 deletions(-) create mode 100644 src/chocolatey.tests/infrastructure.app/services/NugetServiceSpecs.cs diff --git a/src/chocolatey.tests/chocolatey.tests.csproj b/src/chocolatey.tests/chocolatey.tests.csproj index 411590c1d4..0afc2017fd 100644 --- a/src/chocolatey.tests/chocolatey.tests.csproj +++ b/src/chocolatey.tests/chocolatey.tests.csproj @@ -78,6 +78,7 @@ + diff --git a/src/chocolatey.tests/infrastructure.app/services/NugetServiceSpecs.cs b/src/chocolatey.tests/infrastructure.app/services/NugetServiceSpecs.cs new file mode 100644 index 0000000000..e15e31af5f --- /dev/null +++ b/src/chocolatey.tests/infrastructure.app/services/NugetServiceSpecs.cs @@ -0,0 +1,166 @@ +// Copyright © 2011 - Present RealDimensions Software, LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace chocolatey.tests.infrastructure.app.services +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using Moq; + using NuGet; + using chocolatey.infrastructure.app.domain; + using chocolatey.infrastructure.app.services; + using IFileSystem = chocolatey.infrastructure.filesystem.IFileSystem; + + public class NugetServiceSpecs + { + public abstract class NugetServiceSpecsBase : TinySpec + { + protected NugetService service; + protected Mock packageInfoService = new Mock(); + protected Mock fileSystem = new Mock(); + protected Mock nugetLogger = new Mock(); + protected Mock filesService = new Mock(); + protected Mock package = new Mock(); + + public override void Context() + { + fileSystem.ResetCalls(); + nugetLogger.ResetCalls(); + packageInfoService.ResetCalls(); + filesService.ResetCalls(); + package.ResetCalls(); + + service = new NugetService(fileSystem.Object, nugetLogger.Object, packageInfoService.Object, filesService.Object); + } + } + + public class when_NugetService_removes_installation_files_on_uninstall : NugetServiceSpecsBase + { + private Action because; + private ChocolateyPackageInformation packageInfo; + private const string filePath = "c:\\tests"; + private IList packageFiles; + + public override void Context() + { + base.Context(); + package.Setup(x => x.Id).Returns("bob"); + packageInfo = new ChocolateyPackageInformation(package.Object); + packageInfo.FilesSnapshot = new PackageFiles(); + packageFiles = new List(); + fileSystem.Setup(x => x.directory_exists(It.IsAny())).Returns(true); + + } + + public override void Because() + { + because = () => service.remove_installation_files(package.Object, packageInfo); + } + + [Fact] + public void should_do_nothing_if_the_directory_no_longer_exists() + { + Context(); + fileSystem.ResetCalls(); + fileSystem.Setup(x => x.directory_exists(It.IsAny())).Returns(false); + + var packageFile = new PackageFile { Path = filePath, Checksum = "1234" }; + packageFiles.Add(packageFile); + + packageInfo.FilesSnapshot.Files = packageFiles.ToList(); + + var fileSystemFiles = new List() { filePath }; + + fileSystem.Setup(x => x.get_files(It.IsAny(), It.IsAny(), SearchOption.AllDirectories)).Returns(fileSystemFiles); + filesService.Setup(x => x.get_package_file(It.IsAny())).Returns(packageFile); + + because(); + + filesService.Verify(x => x.get_package_file(It.IsAny()), Times.Never); + fileSystem.Verify(x => x.delete_file(filePath),Times.Never); + } + + [Fact] + public void should_remove_an_unchanged_file() + { + Context(); + + var packageFile = new PackageFile { Path = filePath, Checksum = "1234" }; + packageFiles.Add(packageFile); + + packageInfo.FilesSnapshot.Files = packageFiles.ToList(); + + var fileSystemFiles = new List() { filePath }; + + + fileSystem.Setup(x => x.get_files(It.IsAny(), It.IsAny(), SearchOption.AllDirectories)).Returns(fileSystemFiles); + + filesService.Setup(x => x.get_package_file(It.IsAny())).Returns(packageFile); + + because(); + + fileSystem.Verify(x => x.delete_file(filePath)); + } + + [Fact] + public void should_not_delete_a_changed_file() + { + Context(); + + var packageFile = new PackageFile { Path = filePath, Checksum = "1234" }; + var packageFileWithUpdatedChecksum = new PackageFile { Path = filePath, Checksum = "4321" }; + packageFiles.Add(packageFile); + + packageInfo.FilesSnapshot.Files = packageFiles.ToList(); + + var fileSystemFiles = new List() { filePath }; + + + fileSystem.Setup(x => x.get_files(It.IsAny(), It.IsAny(), SearchOption.AllDirectories)).Returns(fileSystemFiles); + + filesService.Setup(x => x.get_package_file(It.IsAny())).Returns(packageFileWithUpdatedChecksum); + + because(); + + fileSystem.Verify(x => x.delete_file(filePath),Times.Never); + } + + [Fact] + public void should_not_delete_an_unfound_file() + { + Context(); + + var packageFile = new PackageFile { Path = filePath, Checksum = "1234" }; + var packageFileNotInOriginal = new PackageFile { Path ="c:\\files", Checksum = "4321" }; + packageFiles.Add(packageFile); + + packageInfo.FilesSnapshot.Files = packageFiles.ToList(); + + var fileSystemFiles = new List() { filePath }; + + + fileSystem.Setup(x => x.get_files(It.IsAny(), It.IsAny(), SearchOption.AllDirectories)).Returns(fileSystemFiles); + + filesService.Setup(x => x.get_package_file(It.IsAny())).Returns(packageFileNotInOriginal); + + because(); + + fileSystem.Verify(x => x.delete_file(filePath),Times.Never); + } + } + } +} \ No newline at end of file diff --git a/src/chocolatey/infrastructure.app/services/FilesService.cs b/src/chocolatey/infrastructure.app/services/FilesService.cs index 2b87891b65..b1793c16d2 100644 --- a/src/chocolatey/infrastructure.app/services/FilesService.cs +++ b/src/chocolatey/infrastructure.app/services/FilesService.cs @@ -74,12 +74,18 @@ public PackageFiles capture_package_files(PackageResult packageResult, Chocolate var files = _fileSystem.get_files(installDirectory, pattern: "*.*", option: SearchOption.AllDirectories); foreach (string file in files.or_empty_list_if_null()) { - var hash = _hashProvider.hash_file(file); - this.Log().Debug(() => " Found '{0}'{1} with checksum '{2}'".format_with(file, Environment.NewLine, hash)); - packageFiles.Files.Add(new PackageFile { Path = file, Checksum = hash}); + packageFiles.Files.Add(get_package_file(file)); } return packageFiles; } + + public PackageFile get_package_file(string file) + { + var hash = _hashProvider.hash_file(file); + this.Log().Debug(() => " Found '{0}'{1} with checksum '{2}'".format_with(file, Environment.NewLine, hash)); + + return new PackageFile { Path = file, Checksum = hash }; + } } } \ No newline at end of file diff --git a/src/chocolatey/infrastructure.app/services/IFilesService.cs b/src/chocolatey/infrastructure.app/services/IFilesService.cs index 7900ac4735..59d2f8c20b 100644 --- a/src/chocolatey/infrastructure.app/services/IFilesService.cs +++ b/src/chocolatey/infrastructure.app/services/IFilesService.cs @@ -45,5 +45,12 @@ public interface IFilesService /// The configuration. /// PackageFiles with entries based on the install location of the package. PackageFiles capture_package_files(PackageResult packageResult, ChocolateyConfiguration config); + + /// + /// Gets a PackageFile from the filepath + /// + /// The file. + /// PackageFile object + PackageFile get_package_file(string file); } } \ No newline at end of file diff --git a/src/chocolatey/infrastructure.app/services/NugetService.cs b/src/chocolatey/infrastructure.app/services/NugetService.cs index 532ac3943d..5c8cacbbf2 100644 --- a/src/chocolatey/infrastructure.app/services/NugetService.cs +++ b/src/chocolatey/infrastructure.app/services/NugetService.cs @@ -42,6 +42,7 @@ public class NugetService : INugetService private readonly IFileSystem _fileSystem; private readonly ILogger _nugetLogger; private readonly IChocolateyPackageInformationService _packageInfoService; + private readonly IFilesService _filesService; private readonly Lazy datetime_initializer = new Lazy(() => new DateTime()); private IDateTime DateTime @@ -55,11 +56,13 @@ private IDateTime DateTime /// The file system. /// The nuget logger /// Package information service - public NugetService(IFileSystem fileSystem, ILogger nugetLogger, IChocolateyPackageInformationService packageInfoService) + /// The files service + public NugetService(IFileSystem fileSystem, ILogger nugetLogger, IChocolateyPackageInformationService packageInfoService, IFilesService filesService) { _fileSystem = fileSystem; _nugetLogger = nugetLogger; _packageInfoService = packageInfoService; + _filesService = filesService; } public void list_noop(ChocolateyConfiguration config) @@ -861,6 +864,7 @@ public ConcurrentDictionary uninstall_run(ChocolateyConfi backup_existing_version(config, packageVersion); packageManager.UninstallPackage(packageVersion, forceRemove: config.Force, removeDependencies: config.ForceDependencies); ensure_nupkg_is_removed(packageVersion, pkgInfo); + remove_installation_files(packageVersion, pkgInfo); } } catch (Exception ex) @@ -900,6 +904,24 @@ private void ensure_nupkg_is_removed(IPackage removedPackage, ChocolateyPackageI _fileSystem.delete_file(nupkg); } + public void remove_installation_files(IPackage removedPackage, ChocolateyPackageInformation pkgInfo) + { + var isSideBySide = pkgInfo != null && pkgInfo.IsSideBySide; + var installDir = _fileSystem.combine_paths(ApplicationParameters.PackagesLocation, "{0}{1}".format_with(removedPackage.Id, isSideBySide ? "." + removedPackage.Version.to_string() : string.Empty)); + + if (_fileSystem.directory_exists(installDir) && pkgInfo != null && pkgInfo.FilesSnapshot != null) + { + foreach (var file in _fileSystem.get_files(installDir, "*.*", SearchOption.AllDirectories).or_empty_list_if_null()) + { + var fileSnapshot = pkgInfo.FilesSnapshot.Files.FirstOrDefault(f => f.Path.is_equal_to(file)); + if (fileSnapshot != null && fileSnapshot.Checksum == _filesService.get_package_file(file).Checksum) + { + _fileSystem.delete_file(file); + } + } + } + } + private void set_package_names_if_all_is_specified(ChocolateyConfiguration config, Action customAction) { if (config.PackageNames.is_equal_to("all"))