diff --git a/Invoke-Tests.ps1 b/Invoke-Tests.ps1 index d6ce96f030..6ff80f79bc 100644 --- a/Invoke-Tests.ps1 +++ b/Invoke-Tests.ps1 @@ -54,12 +54,53 @@ else { if (-not (Test-Path "$TestPath/packages") -or -not $SkipPackaging) { $null = New-Item -Path "$TestPath/packages" -ItemType Directory -Force # Get and pack packages - $nuspecs = Get-ChildItem -Path $PSScriptRoot/src/chocolatey.tests.integration, $PSScriptRoot/tests/packages -Recurse -Include *.nuspec | Where-Object FullName -notmatch 'bin' + $nuspecs = Get-ChildItem -Path $PSScriptRoot/src/chocolatey.tests.integration, $PSScriptRoot/tests/packages -Recurse -Include *.nuspec | Where-Object FullName -NotMatch 'bin' Get-ChildItem -Path $PSScriptRoot/tests/packages -Recurse -Include *.nupkg | Copy-Item -Destination "$TestPath/packages" - foreach ($file in $nuspecs) { - Write-Host "Packaging $file" - $null = choco pack $file.FullName --out "$TestPath/packages" + $packFailures = foreach ($file in $nuspecs) { + # Include allow-unofficial in case an unofficial Chocolatey has been installed globally for testing + $packOutput = choco pack $file.FullName --out "$TestPath/packages" --allow-unofficial + if ($LASTEXITCODE -ne 0) { + [pscustomobject]@{ + Package = $file.FullName + ExitCode = $LASTEXITCODE + Output = $packOutput + } + Write-Warning "Failed to pack $file" + } + else { + Write-Host "Packaged $file" + } + } + + if ($null -ne $packFailures) { + foreach ($failure in $packFailures) { + Write-Warning "$($failure.Package) failed to pack with exit code: $($failure.ExitCode)" + $failure.Output | Write-Warning + } + # If you want to stop things, change this to a throw. + # This is not currently throwing as there are two packages that are supposed to fail. + Write-Error "$($packFailures.Count) packages failed to pack." + } +} + +if (-not (Test-Path "$TestPath/all-packages") -or -not $SkipPackaging) { + $null = New-Item -Path "$TestPath/all-packages" -ItemType Directory -Force + + # These are the package ids that are loaded into the all packages test repository. + $AllPackagesRepository = @( + 'isdependency' + 'hasdependency' + 'hasnesteddependency' + 'downgradesdependency' + 'dependencyfailure' + 'hasfailingnesteddependency' + 'failingdependency' + 'isexactversiondependency' + ) + + foreach ($package in $AllPackagesRepository) { + $null = Copy-Item "$TestPath/packages/$package.*.nupkg" "$TestPath/all-packages/" } } @@ -97,6 +138,7 @@ try { Import-Module $PSScriptRoot\tests\helpers\common-helpers.psm1 -Force $null = Invoke-Choco source add --name hermes --source "$TestPath/packages" + $null = Invoke-Choco source add --name hermes-all --source "$TestPath/all-packages" Enable-ChocolateyFeature -Name allowGlobalConfirmation $PesterConfiguration = [PesterConfiguration]@{ Run = @{ diff --git a/src/chocolatey/StringResources.cs b/src/chocolatey/StringResources.cs index 8f04a3e641..e28c11eb75 100644 --- a/src/chocolatey/StringResources.cs +++ b/src/chocolatey/StringResources.cs @@ -56,5 +56,13 @@ public static class EnvironmentVariables [Browsable(false)] internal const string PackageNuspecVersion = "packageNuspecVersion"; } + + public static class ErrorMessages + { + [EditorBrowsable(EditorBrowsableState.Never)] + [Browsable(false)] + internal const string UnableToDowngrade = "A newer version of {0} (v{1}) is already installed.{2} Use --allow-downgrade or --force to attempt to install older versions."; + internal const string DependencyFailedToInstall = "Failed to install {0} because a previous dependency failed."; + } } } \ 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 8b74d713ae..c701feb980 100644 --- a/src/chocolatey/infrastructure.app/services/NugetService.cs +++ b/src/chocolatey/infrastructure.app/services/NugetService.cs @@ -578,6 +578,12 @@ public virtual ConcurrentDictionary Install(ChocolateyCon foreach (var packageName in packageNames.OrEmpty()) { + if (packageResultsToReturn.ContainsKey(packageName)) + { + // We have already processed this package (likely during dependency resolution of another package). + continue; + } + // We need to ensure we are using a clean configuration file // before we start reading it. config.RevertChanges(); @@ -599,6 +605,7 @@ public virtual ConcurrentDictionary Install(ChocolateyCon if (installedPackage != null && (version == null || version == installedPackage.PackageMetadata.Version) && !config.Force) { var logMessage = "{0} v{1} already installed.{2} Use --force to reinstall, specify a version to install, or try upgrade.".FormatWith(installedPackage.Name, installedPackage.Version, Environment.NewLine); + // We need a temporary PackageResult so that we can add to the Messages collection. var nullResult = packageResultsToReturn.GetOrAdd(packageName, installedPackage); nullResult.Messages.Add(new ResultMessage(ResultType.Warn, logMessage)); nullResult.Messages.Add(new ResultMessage(ResultType.Inconclusive, logMessage)); @@ -619,9 +626,9 @@ public virtual ConcurrentDictionary Install(ChocolateyCon if (installedPackage != null && version != null && version < installedPackage.PackageMetadata.Version && !config.AllowDowngrade) { - var logMessage = "A newer version of {0} (v{1}) is already installed.{2} Use --allow-downgrade or --force to attempt to install older versions.".FormatWith(installedPackage.Name, installedPackage.Version, Environment.NewLine); - var nullResult = packageResultsToReturn.GetOrAdd(packageName, installedPackage); - nullResult.Messages.Add(new ResultMessage(ResultType.Error, logMessage)); + var logMessage = StringResources.ErrorMessages.UnableToDowngrade.FormatWith(installedPackage.Name, installedPackage.Version, Environment.NewLine); + packageResultsToReturn.GetOrAdd(packageName, installedPackage) + .Messages.Add(new ResultMessage(ResultType.Error, logMessage)); this.Log().Error(ChocolateyLoggers.Important, logMessage); continue; } @@ -793,6 +800,26 @@ Version was specified as '{0}'. It is possible that version foreach (SourcePackageDependencyInfo packageDependencyInfo in resolvedPackages) { + // Don't attempt to action this package if dependencies failed. + if (packageDependencyInfo != null && packageResultsToReturn.Any(r => r.Value.Success != true && packageDependencyInfo.Dependencies.Any(d => d.Id.Equals(r.Value.Identity.Id, StringComparison.OrdinalIgnoreCase)))) + { + var logMessage = StringResources.ErrorMessages.DependencyFailedToInstall.FormatWith(packageDependencyInfo.Id); + packageResultsToReturn + .GetOrAdd( + packageDependencyInfo.Id, + new PackageResult(packageDependencyInfo.Id, packageDependencyInfo.Version.ToFullStringChecked(), string.Empty) + ) + .Messages.Add(new ResultMessage(ResultType.Error, logMessage)); + this.Log().Error(ChocolateyLoggers.Important, logMessage); + + if (config.Features.StopOnFirstPackageFailure) + { + throw new ApplicationException("Stopping further execution as {0} has failed install.".FormatWith(packageDependencyInfo.Id)); + } + + continue; + } + var packageRemoteMetadata = packagesToInstall.FirstOrDefault(p => p.Identity.Equals(packageDependencyInfo)); if (packageRemoteMetadata is null) @@ -809,6 +836,22 @@ Version was specified as '{0}'. It is possible that version var packageToUninstall = packagesToUninstall.FirstOrDefault(p => p.PackageMetadata.Id.Equals(packageDependencyInfo.Id, StringComparison.OrdinalIgnoreCase)); if (packageToUninstall != null) { + // Are we attempting a downgrade? We need to ensure it's allowed... + if (!config.AllowDowngrade && packageToUninstall.Identity.HasVersion && packageDependencyInfo.HasVersion && packageDependencyInfo.Version < packageToUninstall.Identity.Version) + { + var logMessage = StringResources.ErrorMessages.UnableToDowngrade.FormatWith(packageToUninstall.Name, packageToUninstall.Version, Environment.NewLine); + packageResultsToReturn.GetOrAdd(packageToUninstall.Name, packageToUninstall) + .Messages.Add(new ResultMessage(ResultType.Error, logMessage)); + this.Log().Error(ChocolateyLoggers.Important, logMessage); + + if (config.Features.StopOnFirstPackageFailure) + { + throw new ApplicationException("Stopping further execution as {0} has failed install.".FormatWith(packageToUninstall.Identity.Id)); + } + + continue; + } + shouldAddForcedResultMessage = true; BackupAndRunBeforeModify(packageToUninstall, config, beforeModifyAction); packageToUninstall.InstallLocation = pathResolver.GetInstallPath(packageToUninstall.Identity); @@ -1051,6 +1094,12 @@ public virtual ConcurrentDictionary Upgrade(ChocolateyCon foreach (var packageName in config.PackageNames.Split(new[] { ApplicationParameters.PackageNamesSeparator }, StringSplitOptions.RemoveEmptyEntries).OrEmpty()) { + if (packageResultsToReturn.ContainsKey(packageName)) + { + // We have already processed this package (likely during dependency resolution of another package). + continue; + } + // We need to ensure we are using a clean configuration file // before we start reading it. config.RevertChanges(); @@ -1138,9 +1187,9 @@ public virtual ConcurrentDictionary Upgrade(ChocolateyCon if (version != null && version < installedPackage.PackageMetadata.Version && !config.AllowDowngrade) { - var logMessage = "A newer version of {0} (v{1}) is already installed.{2} Use --allow-downgrade or --force to attempt to upgrade to older versions.".FormatWith(installedPackage.PackageMetadata.Id, installedPackage.Version, Environment.NewLine); - var nullResult = packageResultsToReturn.GetOrAdd(packageName, new PackageResult(installedPackage.PackageMetadata, pathResolver.GetInstallPath(installedPackage.PackageMetadata.Id))); - nullResult.Messages.Add(new ResultMessage(ResultType.Error, logMessage)); + var logMessage = StringResources.ErrorMessages.UnableToDowngrade.FormatWith(installedPackage.PackageMetadata.Id, installedPackage.Version, Environment.NewLine); + packageResultsToReturn.GetOrAdd(packageName, new PackageResult(installedPackage.PackageMetadata, pathResolver.GetInstallPath(installedPackage.PackageMetadata.Id))) + .Messages.Add(new ResultMessage(ResultType.Error, logMessage)); this.Log().Error(ChocolateyLoggers.Important, logMessage); continue; } @@ -1559,6 +1608,25 @@ public virtual ConcurrentDictionary Upgrade(ChocolateyCon break; } + // Don't attempt to action this package if dependencies failed. + if (packageResultsToReturn.Any(r => r.Value.Success != true && packageDependencyInfo.Dependencies.Any(d => d.Id.Equals(r.Value.Identity.Id, StringComparison.OrdinalIgnoreCase)))) + { + var logMessage = StringResources.ErrorMessages.DependencyFailedToInstall.FormatWith(packageDependencyInfo.Id, packageDependencyInfo.Version); + packageResultsToReturn.GetOrAdd( + packageDependencyInfo.Id, + new PackageResult(packageDependencyInfo.Id, packageDependencyInfo.Version.ToFullStringChecked(), string.Empty) + ) + .Messages.Add(new ResultMessage(ResultType.Error, logMessage)); + this.Log().Error(ChocolateyLoggers.Important, logMessage); + + if (config.Features.StopOnFirstPackageFailure) + { + throw new ApplicationException("Stopping further execution as {0} has failed.".FormatWith(packageDependencyInfo.Id)); + } + + continue; + } + var packageRemoteMetadata = packagesToInstall.FirstOrDefault(p => p.Identity.Equals(packageDependencyInfo)); if (packageRemoteMetadata is null) @@ -1576,6 +1644,32 @@ public virtual ConcurrentDictionary Upgrade(ChocolateyCon { if (packageToUninstall != null) { + // Are we attempting a downgrade? We need to ensure it's allowed... + if (!config.AllowDowngrade && packageToUninstall.Identity.HasVersion && packageDependencyInfo.HasVersion && packageDependencyInfo.Version < packageToUninstall.Identity.Version) + { + var logMessage = StringResources.ErrorMessages.UnableToDowngrade.FormatWith(packageToUninstall.Name, packageToUninstall.Version, Environment.NewLine); + packageResultsToReturn.GetOrAdd(packageToUninstall.Name, packageToUninstall) + .Messages.Add(new ResultMessage(ResultType.Error, logMessage)); + this.Log().Error(ChocolateyLoggers.Important, logMessage); + + if (config.Features.StopOnFirstPackageFailure) + { + throw new ApplicationException("Stopping further execution as {0} has failed.".FormatWith(packageDependencyInfo.Id)); + } + + continue; + } + + // Package was previously marked inconclusive (not upgraded). We need remove the message since we are now upgrading it. + // Packages that are inconclusive but successful are not labeled as successful at the end of the run. + var checkResult = packageResultsToReturn.GetOrAdd(packageToUninstall.Name, packageToUninstall); + + while (checkResult.Inconclusive) + { + checkResult.Messages.Remove(checkResult.Messages.FirstOrDefault((m) => m.MessageType == ResultType.Inconclusive)); + } + + var oldPkgInfo = _packageInfoService.Get(packageToUninstall.PackageMetadata); BackupAndRunBeforeModify(packageToUninstall, oldPkgInfo, config, beforeUpgradeAction); diff --git a/tests/Vagrantfile b/tests/Vagrantfile index a6bd2bf065..ceef065bc1 100644 --- a/tests/Vagrantfile +++ b/tests/Vagrantfile @@ -69,6 +69,7 @@ Vagrant.configure("2") do |config| config.vm.provision "shell", name: "clear-packages", inline: <<-SHELL Write-Host "($(Get-Date -Format "dddd MM/dd/yyyy HH:mm:ss K")) Clearing the packages directory" Remove-Item "$env:TEMP/chocolateyTests/packages" -Recurse -Force -ErrorAction SilentlyContinue + Remove-Item "$env:TEMP/chocolateyTests/all-packages" -Recurse -Force -ErrorAction SilentlyContinue SHELL config.vm.provision "shell", name: "test", inline: <<-SHELL # Copy changed files. diff --git a/tests/helpers/common/Chocolatey/Get-ChocolateyInstalledPackages.ps1 b/tests/helpers/common/Chocolatey/Get-ChocolateyInstalledPackages.ps1 new file mode 100644 index 0000000000..0881ab2bc6 --- /dev/null +++ b/tests/helpers/common/Chocolatey/Get-ChocolateyInstalledPackages.ps1 @@ -0,0 +1,3 @@ +function Get-ChocolateyInstalledPackages { + (Invoke-Choco list -r).Lines | ConvertFrom-ChocolateyOutput -Command List +} diff --git a/tests/packages/dependencyfailure/dependencyfailure.nuspec b/tests/packages/dependencyfailure/dependencyfailure.nuspec new file mode 100644 index 0000000000..4ab6e842ad --- /dev/null +++ b/tests/packages/dependencyfailure/dependencyfailure.nuspec @@ -0,0 +1,23 @@ + + + + dependencyfailure + 1.0.0 + dependencyfailure (Install) + __REPLACE_AUTHORS_OF_SOFTWARE_COMMA_SEPARATED__ + https://_Software_Location_REMOVE_OR_FILL_OUT_ + dependencyfailure admin SPACE_SEPARATED + Package includes a dependency for a package that is known to fail installation. + + Package includes a dependency for a package that is known to fail installation. + + It can be used in tests to ensure that failing dependencies do not allow the package to install. + + + + + + + + + diff --git a/tests/packages/dependencyfailure/tools/chocolateyinstall.ps1 b/tests/packages/dependencyfailure/tools/chocolateyinstall.ps1 new file mode 100644 index 0000000000..719bcd8ac7 --- /dev/null +++ b/tests/packages/dependencyfailure/tools/chocolateyinstall.ps1 @@ -0,0 +1 @@ +Write-Host "I'm successful!" \ No newline at end of file diff --git a/tests/packages/failingdependency/failingdependency.nuspec b/tests/packages/failingdependency/failingdependency.nuspec new file mode 100644 index 0000000000..c8179b5e80 --- /dev/null +++ b/tests/packages/failingdependency/failingdependency.nuspec @@ -0,0 +1,16 @@ + + + + failingdependency + 1.0.0 + dependencyfailure (Install) + __REPLACE_AUTHORS_OF_SOFTWARE_COMMA_SEPARATED__ + https://_Software_Location_REMOVE_OR_FILL_OUT_ + dependencyfailure admin SPACE_SEPARATED + Package that fails to install. Used as part of dependency tree. + Package that fails to install. Used as part of dependency tree. + + + + + diff --git a/tests/packages/failingdependency/tools/chocolateyinstall.ps1 b/tests/packages/failingdependency/tools/chocolateyinstall.ps1 new file mode 100644 index 0000000000..04105d6173 --- /dev/null +++ b/tests/packages/failingdependency/tools/chocolateyinstall.ps1 @@ -0,0 +1,2 @@ +Write-Error "This should fail!" +$env:ChocolateyExitCode = '15608' diff --git a/tests/packages/get-chocolateyunzip-licensed/get-chocolateyunzip-licensed.nuspec b/tests/packages/get-chocolateyunzip-licensed/get-chocolateyunzip-licensed.nuspec index a1daaef7c5..5370f474a7 100755 --- a/tests/packages/get-chocolateyunzip-licensed/get-chocolateyunzip-licensed.nuspec +++ b/tests/packages/get-chocolateyunzip-licensed/get-chocolateyunzip-licensed.nuspec @@ -1,59 +1,23 @@ - - - - - - - - - - - - - get-chocolateyunzip-licensed - - - 3.21.2 - - __REPLACE__ - - - - - __REPLACE__ (Portable) - __REPLACE__ + chocolatey + get-chocolateyunzip-licensed (Portable) + Chocolatey Software - __REPLACE__ + Chocolatey Software get-chocolateyunzip-licensed - __REPLACE__MarkDown_Okay - + + This package contains a commercial edition of the cmdlet `Get-ChocolateyUnzip`, and can be used to verify that installation with that cmdlet works as intended. - - +Additionally it can be tested that the package will fail if being installed on FOSS Edition of Chocolatey. - - - +The package defines the license types `business`, `Education` and `professional` as being valid licenses. + - diff --git a/tests/packages/hasfailingnesteddependency/1.0.0/hasfailingnesteddependency.nuspec b/tests/packages/hasfailingnesteddependency/1.0.0/hasfailingnesteddependency.nuspec new file mode 100644 index 0000000000..6f13009705 --- /dev/null +++ b/tests/packages/hasfailingnesteddependency/1.0.0/hasfailingnesteddependency.nuspec @@ -0,0 +1,27 @@ + + + + hasfailingnesteddependency + 1.0.0 + hasfailingnesteddependency + Chocolatey Software + chocolatey + false + + Package that contains dependencies that have dependencies that may fail to install. + + Used for testing dependency resolution and ensuring dependency failures do not allow package installation. + + Package that contains dependencies that have dependencies that may fail to install. + + + hasfailingnesteddependency admin + + + + + + + + + diff --git a/tests/packages/hasfailingnesteddependency/1.0.0/tools/chocolateyinstall.ps1 b/tests/packages/hasfailingnesteddependency/1.0.0/tools/chocolateyinstall.ps1 new file mode 100644 index 0000000000..d64eb8f47b --- /dev/null +++ b/tests/packages/hasfailingnesteddependency/1.0.0/tools/chocolateyinstall.ps1 @@ -0,0 +1 @@ +Write-Output "$env:PackageName $env:PackageVersion Installed" \ No newline at end of file diff --git a/tests/packages/hasfailingnesteddependency/1.0.0/tools/chocolateyuninstall.ps1 b/tests/packages/hasfailingnesteddependency/1.0.0/tools/chocolateyuninstall.ps1 new file mode 100644 index 0000000000..9ead91ffa3 --- /dev/null +++ b/tests/packages/hasfailingnesteddependency/1.0.0/tools/chocolateyuninstall.ps1 @@ -0,0 +1 @@ +Write-Output "$env:PackageName $env:PackageVersion Uninstalled" \ No newline at end of file diff --git a/tests/packages/hasnesteddependency/1.0.0/hasnesteddependency.nuspec b/tests/packages/hasnesteddependency/1.0.0/hasnesteddependency.nuspec new file mode 100644 index 0000000000..fbc8d2df82 --- /dev/null +++ b/tests/packages/hasnesteddependency/1.0.0/hasnesteddependency.nuspec @@ -0,0 +1,22 @@ + + + + hasnesteddependency + 1.0.0 + hasnesteddependency + Chocolatey Software + chocolatey + false + Package used for testing nested dependencies. + Package used for testing nested dependencies. + + + hasnesteddependency admin + + + + + + + + diff --git a/tests/packages/hasnesteddependency/1.0.0/tools/chocolateyinstall.ps1 b/tests/packages/hasnesteddependency/1.0.0/tools/chocolateyinstall.ps1 new file mode 100644 index 0000000000..d64eb8f47b --- /dev/null +++ b/tests/packages/hasnesteddependency/1.0.0/tools/chocolateyinstall.ps1 @@ -0,0 +1 @@ +Write-Output "$env:PackageName $env:PackageVersion Installed" \ No newline at end of file diff --git a/tests/packages/hasnesteddependency/1.0.0/tools/chocolateyuninstall.ps1 b/tests/packages/hasnesteddependency/1.0.0/tools/chocolateyuninstall.ps1 new file mode 100644 index 0000000000..9ead91ffa3 --- /dev/null +++ b/tests/packages/hasnesteddependency/1.0.0/tools/chocolateyuninstall.ps1 @@ -0,0 +1 @@ +Write-Output "$env:PackageName $env:PackageVersion Uninstalled" \ No newline at end of file diff --git a/tests/packages/test-chocolateypath/test-chocolateypath.nuspec b/tests/packages/test-chocolateypath/test-chocolateypath.nuspec index 9756ecb9c7..580a48463b 100644 --- a/tests/packages/test-chocolateypath/test-chocolateypath.nuspec +++ b/tests/packages/test-chocolateypath/test-chocolateypath.nuspec @@ -1,81 +1,17 @@ - - - - - - - - - - - - - - test-chocolateypath - - - 0.1.0 - - - - - - - test-packagepath (Install) __REPLACE_AUTHORS_OF_SOFTWARE_COMMA_SEPARATED__ - https://_Software_Location_REMOVE_OR_FILL_OUT_ - - - - - - - - - - test-packagepath SPACE_SEPARATED - __REPLACE__ - __REPLACE__MarkDown_Okay - - - - - - - - - - + Package to test the Get-ChocolateyPath Chocolatey PowerShell function. + Package to test the Get-ChocolateyPath Chocolatey PowerShell function. - diff --git a/tests/packages/upgradedowngradesdependency/1.0.0/downgradesdependency.nuspec b/tests/packages/upgradedowngradesdependency/1.0.0/downgradesdependency.nuspec new file mode 100644 index 0000000000..0ad0f09d93 --- /dev/null +++ b/tests/packages/upgradedowngradesdependency/1.0.0/downgradesdependency.nuspec @@ -0,0 +1,29 @@ + + + + downgradesdependency + 1.0.0 + Has out of range Dependency + Chocolatey Software + chocolatey + false + + These packages can be used to test the installation or upgrading of packages that require an existing package to downgrade. + +Each version is available as `upgradedowngradesdependency` and `downgradesdependency`. This is to allow testing of scenarios where `choco upgrade all` would process the dependency before and after the parent package. + +- Version 1.0.0 contains a range that can be used in an upgrade scenario and has a dependency on `isdependency 1.0.0 or greater` +- Version 2.0.0 contains an exact dependency on `isdependency` with a version of `1.0.0` + + Package to test for out of range dependencies. This 1st version have a valid exact range. + + + upgradedowngradesdependency admin + + + + + + + + diff --git a/tests/packages/upgradedowngradesdependency/1.0.0/tools/chocolateybeforemodify.ps1 b/tests/packages/upgradedowngradesdependency/1.0.0/tools/chocolateybeforemodify.ps1 new file mode 100644 index 0000000000..7d0b91fece --- /dev/null +++ b/tests/packages/upgradedowngradesdependency/1.0.0/tools/chocolateybeforemodify.ps1 @@ -0,0 +1 @@ +Write-Output "Upgrading or Uninstalling $env:PackageName $env:PackageVersion" \ No newline at end of file diff --git a/tests/packages/upgradedowngradesdependency/1.0.0/tools/chocolateyinstall.ps1 b/tests/packages/upgradedowngradesdependency/1.0.0/tools/chocolateyinstall.ps1 new file mode 100644 index 0000000000..d64eb8f47b --- /dev/null +++ b/tests/packages/upgradedowngradesdependency/1.0.0/tools/chocolateyinstall.ps1 @@ -0,0 +1 @@ +Write-Output "$env:PackageName $env:PackageVersion Installed" \ No newline at end of file diff --git a/tests/packages/upgradedowngradesdependency/1.0.0/tools/chocolateyuninstall.ps1 b/tests/packages/upgradedowngradesdependency/1.0.0/tools/chocolateyuninstall.ps1 new file mode 100644 index 0000000000..9ead91ffa3 --- /dev/null +++ b/tests/packages/upgradedowngradesdependency/1.0.0/tools/chocolateyuninstall.ps1 @@ -0,0 +1 @@ +Write-Output "$env:PackageName $env:PackageVersion Uninstalled" \ No newline at end of file diff --git a/tests/packages/upgradedowngradesdependency/1.0.0/upgradedowngradesdependency.nuspec b/tests/packages/upgradedowngradesdependency/1.0.0/upgradedowngradesdependency.nuspec new file mode 100644 index 0000000000..bb723ce36e --- /dev/null +++ b/tests/packages/upgradedowngradesdependency/1.0.0/upgradedowngradesdependency.nuspec @@ -0,0 +1,29 @@ + + + + upgradedowngradesdependency + 1.0.0 + Has out of range Dependency + Chocolatey Software + chocolatey + false + + These packages can be used to test the installation or upgrading of packages that require an existing package to downgrade. + +Each version is available as `upgradedowngradesdependency` and `downgradesdependency`. This is to allow testing of scenarios where `choco upgrade all` would process the dependency before and after the parent package. + +- Version 1.0.0 contains a range that can be used in an upgrade scenario and has a dependency on `isdependency 1.0.0 or greater` +- Version 2.0.0 contains an exact dependency on `isdependency` with a version of `1.0.0` + + Package to test for out of range dependencies. This 1st version have a valid exact range. + + + upgradedowngradesdependency admin + + + + + + + + diff --git a/tests/packages/upgradedowngradesdependency/2.0.0/downgradesdependency.nuspec b/tests/packages/upgradedowngradesdependency/2.0.0/downgradesdependency.nuspec new file mode 100644 index 0000000000..efccc67761 --- /dev/null +++ b/tests/packages/upgradedowngradesdependency/2.0.0/downgradesdependency.nuspec @@ -0,0 +1,29 @@ + + + + downgradesdependency + 2.0.0 + Has out of range Dependency (Below available) + Chocolatey Software + chocolatey + false + + These packages can be used to test the installation or upgrading of packages that require an existing package to downgrade. + +Each version is available as `upgradedowngradesdependency` and `downgradesdependency`. This is to allow testing of scenarios where `choco upgrade all` would process the dependency before and after the parent package. + +- Version 1.0.0 contains a range that can be used in an upgrade scenario and has a dependency on `isdependency 1.0.0 or greater` +- Version 2.0.0 contains an exact dependency on `isdependency` with a version of `1.0.0` + + Package to test for out of range dependencies. This version uses a dependency that require a lower version. + + + upgradedowngradesdependency admin + + + + + + + + diff --git a/tests/packages/upgradedowngradesdependency/2.0.0/tools/chocolateybeforemodify.ps1 b/tests/packages/upgradedowngradesdependency/2.0.0/tools/chocolateybeforemodify.ps1 new file mode 100644 index 0000000000..7d0b91fece --- /dev/null +++ b/tests/packages/upgradedowngradesdependency/2.0.0/tools/chocolateybeforemodify.ps1 @@ -0,0 +1 @@ +Write-Output "Upgrading or Uninstalling $env:PackageName $env:PackageVersion" \ No newline at end of file diff --git a/tests/packages/upgradedowngradesdependency/2.0.0/tools/chocolateyinstall.ps1 b/tests/packages/upgradedowngradesdependency/2.0.0/tools/chocolateyinstall.ps1 new file mode 100644 index 0000000000..d64eb8f47b --- /dev/null +++ b/tests/packages/upgradedowngradesdependency/2.0.0/tools/chocolateyinstall.ps1 @@ -0,0 +1 @@ +Write-Output "$env:PackageName $env:PackageVersion Installed" \ No newline at end of file diff --git a/tests/packages/upgradedowngradesdependency/2.0.0/tools/chocolateyuninstall.ps1 b/tests/packages/upgradedowngradesdependency/2.0.0/tools/chocolateyuninstall.ps1 new file mode 100644 index 0000000000..9ead91ffa3 --- /dev/null +++ b/tests/packages/upgradedowngradesdependency/2.0.0/tools/chocolateyuninstall.ps1 @@ -0,0 +1 @@ +Write-Output "$env:PackageName $env:PackageVersion Uninstalled" \ No newline at end of file diff --git a/tests/packages/upgradedowngradesdependency/2.0.0/upgradedowngradesdependency.nuspec b/tests/packages/upgradedowngradesdependency/2.0.0/upgradedowngradesdependency.nuspec new file mode 100644 index 0000000000..e00f3c9dea --- /dev/null +++ b/tests/packages/upgradedowngradesdependency/2.0.0/upgradedowngradesdependency.nuspec @@ -0,0 +1,29 @@ + + + + upgradedowngradesdependency + 2.0.0 + Has out of range Dependency (Below available) + Chocolatey Software + chocolatey + false + + These packages can be used to test the installation or upgrading of packages that require an existing package to downgrade. + +Each version is available as `upgradedowngradesdependency` and `downgradesdependency`. This is to allow testing of scenarios where `choco upgrade all` would process the dependency before and after the parent package. + +- Version 1.0.0 contains a range that can be used in an upgrade scenario and has a dependency on `isdependency 1.0.0 or greater` +- Version 2.0.0 contains an exact dependency on `isdependency` with a version of `1.0.0` + + Package to test for out of range dependencies. This version uses a dependency that require a lower version. + + + upgradedowngradesdependency admin + + + + + + + + diff --git a/tests/packages/upgradedowngradesdependency/Readme.md b/tests/packages/upgradedowngradesdependency/Readme.md new file mode 100644 index 0000000000..0f2c4f806f --- /dev/null +++ b/tests/packages/upgradedowngradesdependency/Readme.md @@ -0,0 +1,6 @@ +These packages can be used to test the installation or upgrading of packages that require an existing package to downgrade. + +Each version is available as `upgradedowngradesdependency` and `downgradesdependency`. This is to allow testing of scenarios where `choco upgrade all` would process the dependency before and after the parent package. + +- Version 1.0.0 contains a range that can be used in an upgrade scenario and has a dependency on `isdependency 1.0.0 or greater` +- Version 2.0.0 contains an exact dependency on `isdependency` with a version of `1.0.0` diff --git a/tests/pester-tests/commands/choco-install.Tests.ps1 b/tests/pester-tests/commands/choco-install.Tests.ps1 index 9cfa0f1a95..cbac68a900 100644 --- a/tests/pester-tests/commands/choco-install.Tests.ps1 +++ b/tests/pester-tests/commands/choco-install.Tests.ps1 @@ -217,7 +217,8 @@ It "Should mention that package hash verification was skipped since local folder source is being used" { if ((Test-HasNuGetV3Source) -or (-not $env:TEST_KITCHEN)) { $Output.Lines | Should -Contain "Source does not provide a package hash, skipping package hash validation." -Because $Output.String - } else { + } + else { $Output.Lines | Should -Contain "Package hash matches expected hash." -Because $Output.String } } @@ -1690,7 +1691,7 @@ To install a local, or remote file, you may use: $PackageUnderTest = "installpackage", "packagewithscript" - $Output = "a`n"*2 | Invoke-Choco install @PackageUnderTest + $Output = "a`n" * 2 | Invoke-Choco install @PackageUnderTest } It "Installs successfully and exits with success (0)" { @@ -1740,7 +1741,8 @@ To install a local, or remote file, you may use: It 'Outputs download completed' { $testMessage = if ($features.License) { "Download of 'cmake-3.21.2-windows-x86_64.zip' (36.01 MB) completed." - } else { + } + else { "Download of cmake-3.21.2-windows-x86_64.zip (36.01 MB) completed." } $Output.Lines | Should -Contain $testMessage -Because $Output.String @@ -1749,7 +1751,8 @@ To install a local, or remote file, you may use: It 'Outputs extracting correct archive' { $testMessage = if ($features.License) { "Extracting cmake-3.21.2-windows-x86_64.zip to $env:ChocolateyInstall\lib\install-chocolateyzip\tools..." - } else { + } + else { "Extracting $($paths.CachePathLong)\install-chocolateyzip\3.21.2\cmake-3.21.2-windows-x86_64.zip to $env:ChocolateyInstall\lib\install-chocolateyzip\tools..." } $Output.Lines | Should -Contain $testMessage -Because $Output.String @@ -1925,8 +1928,8 @@ To install a local, or remote file, you may use: } Context "Installing a package with a non-normalized version number" -ForEach @( - @{ ExpectedPackageVersion = '1.0.0' ; SearchVersion = '1' ; NuspecVersion = '01.0.0.0'} - @{ ExpectedPackageVersion = '1.0.0' ; SearchVersion = '1.0' ; NuspecVersion = '01.0.0.0'} + @{ ExpectedPackageVersion = '1.0.0' ; SearchVersion = '1' ; NuspecVersion = '01.0.0.0' } + @{ ExpectedPackageVersion = '1.0.0' ; SearchVersion = '1.0' ; NuspecVersion = '01.0.0.0' } @{ ExpectedPackageVersion = '1.0.0' ; SearchVersion = '1.0.0' ; NuspecVersion = '01.0.0.0' } @{ ExpectedPackageVersion = '4.0.1' ; SearchVersion = '4.0.1' ; NuspecVersion = '004.0.01.0' } @{ ExpectedPackageVersion = '1.0.0' ; SearchVersion = '01.0.0.0' ; NuspecVersion = '01.0.0.0' } @@ -2027,21 +2030,21 @@ To install a local, or remote file, you may use: } It 'Outputs as ' -ForEach @(@{ - Name = 'chocolateyPackageVersion' - Value= '0.9.0' - } - @{ - Name = 'packageVersion' - Value= '0.9.0' - } - @{ - Name = 'chocolateyPackageNuspecVersion' - Value= '0.9' - } - @{ - Name = 'packageNuspecVersion' - Value= '0.9' - }) { + Name = 'chocolateyPackageVersion' + Value = '0.9.0' + } + @{ + Name = 'packageVersion' + Value = '0.9.0' + } + @{ + Name = 'chocolateyPackageNuspecVersion' + Value = '0.9' + } + @{ + Name = 'packageNuspecVersion' + Value = '0.9' + }) { $Output.Lines | Should -Contain "$Name=$Value" } } @@ -2070,6 +2073,203 @@ To install a local, or remote file, you may use: } } + Context 'Installing a package with argument () should () downgrade an existing package dependency.' -Tag Downgrade -ForEach @( + @{ + Argument = '--force' + AllowsDowngrade = $true + ExpectedExit = 0 + } + @{ + Argument = '--allow-downgrade' + AllowsDowngrade = $true + ExpectedExit = 0 + } + @{ + Argument = '' + AllowsDowngrade = $false + ExpectedExit = 1 + } + ) { + BeforeAll { + $DependentPackage = @{ + Name = 'isdependency' + Version = '2.1.0' + } + + Restore-ChocolateyInstallSnapshot + + $Setup = Invoke-Choco install $DependentPackage.Name --version $DependentPackage.Version --confirm + $Output = Invoke-Choco install downgradesdependency --confirm $Argument + $Packages = Get-ChocolateyInstalledPackages + } + + It "Exits correctly ()" { + $Output.ExitCode | Should -Be $ExpectedExit -Because $Output.String + } + + It "Reports that it can () downgrade isdependency" { + if ($AllowsDowngrade) { + $Output.Lines | Should -Contain 'Chocolatey installed 2/2 packages.' -Because $Output.String + } + else { + $Output.Lines | Should -Contain 'Chocolatey installed 0/2 packages. 2 packages failed.' -Because $Output.String + } + + $Output.Lines | Should -Contain -Not:($AllowsDowngrade) "A newer version of $($DependentPackage.Name) (v$($DependentPackage.Version)) is already installed." -Because $Output.String + $Output.Lines | Should -Contain -Not:($AllowsDowngrade) 'Use --allow-downgrade or --force to attempt to install older versions.' -Because $Output.String + } + + It "Should have the expected packages" { + if ($AllowsDowngrade) { + $Packages | where { $_.Name -eq $DependentPackage.Name -and $_.Version -eq '1.0.0' } | Should -Not -BeNullOrEmpty -Because "Packages: $Packages $($Output.String)" + } + else { + $Packages | where { $_.Name -eq $DependentPackage.Name -and $_.Version -eq $DependentPackage.Version } | Should -Not -BeNullOrEmpty -Because "Packages: $Packages $($Output.String)" + } + } + } + + Context 'Installing a package with argument () and StopOnFirstPackageFailure enabled should () downgrade an existing package dependency and correctly handle installpackage.' -Tag Downgrade, StopOnFirstPackageFailure -ForEach @( + @{ + Argument = '--force' + AllowsDowngrade = $true + ExpectedExit = 0 + } + @{ + Argument = '--allow-downgrade' + AllowsDowngrade = $true + ExpectedExit = 0 + } + @{ + Argument = '' + AllowsDowngrade = $false + ExpectedExit = 1 + } + ) { + BeforeAll { + $DependentPackage = @{ + Name = 'isdependency' + Version = '2.1.0' + } + + Restore-ChocolateyInstallSnapshot + + $Setup = Invoke-Choco install $DependentPackage.Name --version $DependentPackage.Version --confirm + $null = Enable-ChocolateyFeature -Name StopOnFirstPackageFailure + $Output = Invoke-Choco install downgradesdependency installpackage --confirm $Argument + $Packages = Get-ChocolateyInstalledPackages + } + + It "Exits correctly ()" { + $Output.ExitCode | Should -Be $ExpectedExit -Because $Output.String + } + + It "Reports that it can () downgrade isdependency" { + $Output.Lines | Should -Contain -Not:($AllowsDowngrade) "A newer version of $($DependentPackage.Name) (v$($DependentPackage.Version)) is already installed." -Because $Output.String + $Output.Lines | Should -Contain -Not:($AllowsDowngrade) 'Use --allow-downgrade or --force to attempt to install older versions.' -Because $Output.String + } + + It "Should have the expected packages" { + if ($AllowsDowngrade) { + $Packages | where { $_.Name -eq $DependentPackage.Name -and $_.Version -eq '1.0.0' } | Should -Not -BeNullOrEmpty -Because "Packages: $Packages $($Output.String)" + } + else { + $Packages | where { $_.Name -eq $DependentPackage.Name -and $_.Version -eq $DependentPackage.Version } | Should -Not -BeNullOrEmpty -Because "Packages: $Packages $($Output.String)" + } + } + } + + Context 'Installing a package () with a failing nested dependency should not install the package with failing dependencies' -ForEach @( + @{ + PackageName = 'dependencyfailure;downgradesdependency;failingdependency;hasdependency;hasfailingnesteddependency;hasnesteddependency;isdependency;isexactversiondependency'.Split(';') + } + @{ + PackageName = @('packages.config') + } + @{ + PackageName = @('all') + } + ) { + BeforeAll { + + Restore-ChocolateyInstallSnapshot -SetWorkingDirectory + + Copy-Item "$PSScriptRoot/failingnested.packages.config" './packages.config' + Disable-ChocolateySource -All + Enable-ChocolateySource -Name 'hermes-all' + + $Output = Invoke-Choco install @PackageName --confirm + $Packages = Get-ChocolateyInstalledPackages + } + + It "Exits correctly (15608)" { + # failingdependency exits with 15608, so Chocolatey exits with that. + $Output.ExitCode | Should -Be 15608 -Because $Output.String + } + + It "Should have the expected packages" { + $ExpectedPackages = @( + "downgradesdependency" + "isdependency" + "isexactversiondependency" + ) + $UnexpectedPackages = @( + 'dependencyfailure' + "dependencyfailure" + 'hasfailingnesteddependency' + ) + + foreach ($package in $ExpectedPackages) { + $Packages.Name | Should -Contain $package -Because "Package: $package $($Output.String)" + } + + foreach ($package in $UnexpectedPackages) { + $Packages.Name | Should -Not -Contain $package -Because "Package: $package $($Output.String)" + } + } + } + + Context 'Installing a package () with a failing nested dependency should not install the package with failing dependencies' -ForEach @( @{ PackageName = 'hasfailingnesteddependency;hasnesteddependency' } ) { + BeforeAll { + + Restore-ChocolateyInstallSnapshot -SetWorkingDirectory + + Disable-ChocolateySource -All + Enable-ChocolateySource -Name 'hermes-all' + + $Output = Invoke-Choco install $PackageName --confirm + $Packages = Get-ChocolateyInstalledPackages + } + + It "Exits correctly (15608)" { + # failingdependency exits with 15608, so Chocolatey exits with that. + $Output.ExitCode | Should -Be 15608 -Because $Output.String + } + + It "Should have the expected packages" { + $ExpectedPackages = @( + "downgradesdependency" + "hasnesteddependency" + "isdependency" + "hasdependency" + ) + $UnexpectedPackages = @( + "dependencyfailure" + "isexactversiondependency" + 'dependencyfailure' + 'hasfailingnesteddependency' + ) + + foreach ($package in $ExpectedPackages) { + $Packages.Name | Should -Contain $package -Because "Package: $package $($Output.String)" + } + + foreach ($package in $UnexpectedPackages) { + $Packages.Name | Should -Not -Contain $package -Because "Package: $package $($Output.String)" + } + } + } + # This needs to be the last test in this block, to ensure NuGet configurations aren't being created. # Any tests after this block are expected to generate the configuration as they're explicitly using the NuGet CLI Test-NuGetPaths diff --git a/tests/pester-tests/commands/choco-upgrade.Tests.ps1 b/tests/pester-tests/commands/choco-upgrade.Tests.ps1 index 460b72ec64..0eb1329ba4 100644 --- a/tests/pester-tests/commands/choco-upgrade.Tests.ps1 +++ b/tests/pester-tests/commands/choco-upgrade.Tests.ps1 @@ -736,6 +736,102 @@ To upgrade a local, or remote file, you may use: } } + Context 'Upgrading a package () with should downgrade () an existing package dependency.' -Tag Downgrade -ForEach @( + @{ + Argument = '--force' + AllowsDowngrade = $true + ExpectedExit = 0 + PackageName = 'upgradedowngradesdependency' + BasePackage = 'upgradedowngradesdependency' + } + @{ + Argument = '--allow-downgrade' + AllowsDowngrade = $true + ExpectedExit = 0 + PackageName = 'upgradedowngradesdependency' + BasePackage = 'upgradedowngradesdependency' + } + @{ + Argument = '' + AllowsDowngrade = $false + ExpectedExit = 1 + PackageName = 'upgradedowngradesdependency' + BasePackage = 'upgradedowngradesdependency' + } + @{ + Argument = '--force' + AllowsDowngrade = $true + ExpectedExit = 0 + PackageName = 'all' + BasePackage = 'upgradedowngradesdependency' + } + @{ + Argument = '--allow-downgrade' + AllowsDowngrade = $true + ExpectedExit = 0 + PackageName = 'all' + BasePackage = 'upgradedowngradesdependency' + } + @{ + Argument = '' + AllowsDowngrade = $false + ExpectedExit = 1 + PackageName = 'all' + BasePackage = 'upgradedowngradesdependency' + } + @{ + Argument = '--force' + AllowsDowngrade = $true + ExpectedExit = 0 + PackageName = 'all' + BasePackage = 'downgradesdependency' + } + @{ + Argument = '--allow-downgrade' + AllowsDowngrade = $true + ExpectedExit = 0 + PackageName = 'all' + BasePackage = 'downgradesdependency' + } + @{ + Argument = '' + AllowsDowngrade = $false + ExpectedExit = 1 + PackageName = 'all' + BasePackage = 'downgradesdependency' + } + ) { + BeforeAll { + $DependentPackage = @{ + Name = 'isdependency' + Version = '2.1.0' + } + + Restore-ChocolateyInstallSnapshot + + $Setup = Invoke-Choco install $DependentPackage.Name --version $DependentPackage.Version --confirm + $Setup2 = Invoke-Choco install $BasePackage --version 1.0.0 --confirm + $Output = Invoke-Choco upgrade $PackageName --confirm --except="'chocolatey'" $Argument + } + + It "Exits with expected result ()" { + $Output.ExitCode | Should -Be $ExpectedExit -Because $Output.String + } + + It "Reports correctly about downgrading isdependency" { + if ($AllowsDowngrade) { + $Output.Lines | Should -Not -Contain 'A newer version of isdependency (v2.1.0) is already installed.' -Because $Output.String + $Output.Lines | Should -Not -Contain 'Use --allow-downgrade or --force to attempt to install older versions.' -Because $Output.String + $Output.String | Should -MatchExactly 'Chocolatey upgraded 2/\d+ packages.' + } + else { + $Output.Lines | Should -Contain 'A newer version of isdependency (v2.1.0) is already installed.' -Because $Output.String + $Output.Lines | Should -Contain 'Use --allow-downgrade or --force to attempt to install older versions.' -Because $Output.String + $Output.String | Should -MatchExactly 'Chocolatey upgraded 0/\d+ packages. 2 packages failed.' + } + } + } + # This needs to be (almost) the last test in this block, to ensure NuGet configurations aren't being created. # Any tests after this block are expected to generate the configuration as they're explicitly using the NuGet CLI Test-NuGetPaths diff --git a/tests/pester-tests/commands/failingnested.packages.config b/tests/pester-tests/commands/failingnested.packages.config new file mode 100644 index 0000000000..698f931c4f --- /dev/null +++ b/tests/pester-tests/commands/failingnested.packages.config @@ -0,0 +1,11 @@ + + + + + + + + + + +