diff --git a/src/chocolatey.tests.integration/NUnitSetup.cs b/src/chocolatey.tests.integration/NUnitSetup.cs index f057a9e5a9..d2e187acc4 100644 --- a/src/chocolatey.tests.integration/NUnitSetup.cs +++ b/src/chocolatey.tests.integration/NUnitSetup.cs @@ -1,12 +1,12 @@ // 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. @@ -15,10 +15,12 @@ namespace chocolatey.tests.integration { + using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; + using System.Threading; using NUnit.Framework; using SimpleInjector; using chocolatey.infrastructure.app; @@ -41,25 +43,27 @@ public override void BeforeEverything() { Container = SimpleInjectorContainer.Container; fix_application_parameter_variables(Container); - var config = Container.GetInstance(); - config.Information.PlatformType = PlatformType.Windows; - //config.Information.IsInteractive = false; - //config.PromptForConfirmation = false; - var force = config.Force; + base.BeforeEverything(); + + // deep copy so we don't have the same configuration and + // don't have to worry about issues using it + var config = Container.GetInstance().deep_copy(); + config.Information.PlatformType = PlatformType.Windows; + config.Information.IsInteractive = false; + config.PromptForConfirmation = false; config.Force = true; + unpack_self(Container, config); build_packages(Container, config); - unpack_self(Container,config); - config.Force = force; - - base.BeforeEverything(); ConfigurationBuilder.set_up_configuration(new List(), config, Container.GetInstance(), Container.GetInstance(), null); + + MockLogger.reset(); } /// - /// Most of the application parameters are already set by runtime and are readonly values. - /// They need to be updated, so we can do that with reflection. + /// Most of the application parameters are already set by runtime and are readonly values. + /// They need to be updated, so we can do that with reflection. /// private static void fix_application_parameter_variables(Container container) { @@ -67,31 +71,31 @@ private static void fix_application_parameter_variables(Container container) var applicationLocation = fileSystem.get_directory_name(fileSystem.get_current_assembly_path()); - var field = typeof (ApplicationParameters).GetField("InstallLocation"); + var field = typeof(ApplicationParameters).GetField("InstallLocation"); field.SetValue(null, applicationLocation); - field = typeof (ApplicationParameters).GetField("LicenseFileLocation"); + field = typeof(ApplicationParameters).GetField("LicenseFileLocation"); field.SetValue(null, fileSystem.combine_paths(ApplicationParameters.InstallLocation, "license", "chocolatey.license.xml")); - field = typeof (ApplicationParameters).GetField("LoggingLocation"); + field = typeof(ApplicationParameters).GetField("LoggingLocation"); field.SetValue(null, fileSystem.combine_paths(ApplicationParameters.InstallLocation, "logs")); - field = typeof (ApplicationParameters).GetField("GlobalConfigFileLocation"); + field = typeof(ApplicationParameters).GetField("GlobalConfigFileLocation"); field.SetValue(null, fileSystem.combine_paths(ApplicationParameters.InstallLocation, "config", "chocolatey.config")); - field = typeof (ApplicationParameters).GetField("PackagesLocation"); + field = typeof(ApplicationParameters).GetField("PackagesLocation"); field.SetValue(null, fileSystem.combine_paths(ApplicationParameters.InstallLocation, "lib")); - field = typeof (ApplicationParameters).GetField("PackageFailuresLocation"); + field = typeof(ApplicationParameters).GetField("PackageFailuresLocation"); field.SetValue(null, fileSystem.combine_paths(ApplicationParameters.InstallLocation, "lib-bad")); field = typeof(ApplicationParameters).GetField("PackageBackupLocation"); field.SetValue(null, fileSystem.combine_paths(ApplicationParameters.InstallLocation, "lib-bkp")); - field = typeof (ApplicationParameters).GetField("ShimsLocation"); + field = typeof(ApplicationParameters).GetField("ShimsLocation"); field.SetValue(null, fileSystem.combine_paths(ApplicationParameters.InstallLocation, "bin")); - field = typeof (ApplicationParameters).GetField("ChocolateyPackageInfoStoreLocation"); + field = typeof(ApplicationParameters).GetField("ChocolateyPackageInfoStoreLocation"); field.SetValue(null, fileSystem.combine_paths(ApplicationParameters.InstallLocation, ".chocolatey")); // we need to speed up specs a bit, so only try filesystem locking operations twice @@ -104,44 +108,44 @@ private static void fix_application_parameter_variables(Container container) private void unpack_self(Container container, ChocolateyConfiguration config) { - var unpackCommand = container.GetInstance(); + var unpackCommand = container.GetInstance(); unpackCommand.run(config); } private void build_packages(Container container, ChocolateyConfiguration config) { - var input = config.Input; - var fileSystem = container.GetInstance(); var contextDir = fileSystem.combine_paths(fileSystem.get_directory_name(fileSystem.get_current_assembly_path()), "context"); - + // short-circuit building packages if they are already there. if (fileSystem.get_files(contextDir, "*.nupkg").Any()) { - System.Console.WriteLine("Packages have already been built. Skipping... - If you need to rebuild packages, delete all nupkg files in {0}.".format_with(contextDir)); + Console.WriteLine("Packages have already been built. Skipping... - If you need to rebuild packages, delete all nupkg files in {0}.".format_with(contextDir)); return; } var files = fileSystem.get_files(contextDir, "*.nuspec", SearchOption.AllDirectories); - - var command = container.GetInstance(); + + var command = container.GetInstance(); foreach (var file in files.or_empty_list_if_null()) { config.Input = file; - System.Console.WriteLine("Building {0}".format_with(file)); + Console.WriteLine("Building {0}".format_with(file)); command.run(config); } - System.Console.WriteLine("Moving all nupkgs in {0} to context directory.".format_with(fileSystem.get_current_directory())); + Console.WriteLine("Moving all nupkgs in {0} to context directory.".format_with(fileSystem.get_current_directory())); var nupkgs = fileSystem.get_files(fileSystem.get_current_directory(), "*.nupkg"); foreach (var nupkg in nupkgs.or_empty_list_if_null()) { - fileSystem.copy_file(nupkg,fileSystem.combine_paths(contextDir,fileSystem.get_file_name(nupkg)),overwriteExisting:true); + fileSystem.copy_file(nupkg, fileSystem.combine_paths(contextDir, fileSystem.get_file_name(nupkg)), overwriteExisting: true); fileSystem.delete_file(nupkg); } - - config.Input = input; + + //concurrency issues when packages are first built out during testing + Thread.Sleep(2000); + Console.WriteLine("Continuing with tests now after waiting for files to finish moving."); } } diff --git a/src/chocolatey.tests.integration/Scenario.cs b/src/chocolatey.tests.integration/Scenario.cs index 5cd0f3b9d2..3a5b5b60d5 100644 --- a/src/chocolatey.tests.integration/Scenario.cs +++ b/src/chocolatey.tests.integration/Scenario.cs @@ -86,22 +86,13 @@ public static void install_package(ChocolateyConfiguration config, string packag { _service = NUnitSetup.Container.GetInstance(); } - - var originalPackageName = config.PackageNames; - var originalPackageVersion = config.Version; - - config.PackageNames = packageId; - config.Version = version; - _service.install_run(config); - config.PackageNames = originalPackageName; - config.Version = originalPackageVersion; - //var pattern = "{0}.{1}{2}".format_with(packageId, string.IsNullOrWhiteSpace(version) ? "*" : version, Constants.PackageExtension); - //var files = _fileSystem.get_files(config.Sources, pattern); - //foreach (var file in files) - //{ - // var packageManager = NugetCommon.GetPackageManager(config, new ChocolateyNugetLogger(), null, null, false); - // packageManager.InstallPackage(new OptimizedZipPackage(file), false,false); - //} + var installConfig = config.deep_copy(); + + installConfig.PackageNames = packageId; + installConfig.Version = version; + _service.install_run(installConfig); + + NUnitSetup.MockLogger.Messages.Clear(); } private static ChocolateyConfiguration baseline_configuration() diff --git a/src/chocolatey.tests.integration/chocolatey.tests.integration.csproj b/src/chocolatey.tests.integration/chocolatey.tests.integration.csproj index d1ab6852b8..4decad22d0 100644 --- a/src/chocolatey.tests.integration/chocolatey.tests.integration.csproj +++ b/src/chocolatey.tests.integration/chocolatey.tests.integration.csproj @@ -312,6 +312,12 @@ Always + + Always + + + Always + Always diff --git a/src/chocolatey.tests.integration/context/nonterminatingerror/1.0/nonterminatingerror.nuspec b/src/chocolatey.tests.integration/context/nonterminatingerror/1.0/nonterminatingerror.nuspec new file mode 100644 index 0000000000..c7f158d329 --- /dev/null +++ b/src/chocolatey.tests.integration/context/nonterminatingerror/1.0/nonterminatingerror.nuspec @@ -0,0 +1,13 @@ + + + + nonterminatingerror + 1.0 + Rob Reynolds + Rob Reynolds + nonterminating - This package errors during install with a non-terminating error + + + + + \ No newline at end of file diff --git a/src/chocolatey.tests.integration/context/nonterminatingerror/1.0/tools/chocolateyInstall.ps1 b/src/chocolatey.tests.integration/context/nonterminatingerror/1.0/tools/chocolateyInstall.ps1 new file mode 100644 index 0000000000..805a6faaba --- /dev/null +++ b/src/chocolatey.tests.integration/context/nonterminatingerror/1.0/tools/chocolateyInstall.ps1 @@ -0,0 +1 @@ +Write-Error "Oh no! An error" \ No newline at end of file diff --git a/src/chocolatey.tests.integration/scenarios/InstallScenarios.cs b/src/chocolatey.tests.integration/scenarios/InstallScenarios.cs index d3193abbae..a414604ab3 100644 --- a/src/chocolatey.tests.integration/scenarios/InstallScenarios.cs +++ b/src/chocolatey.tests.integration/scenarios/InstallScenarios.cs @@ -1142,6 +1142,189 @@ public void should_have_expected_error_in_package_result() } } + [Concern(typeof(ChocolateyInstallCommand))] + public class when_installing_a_package_that_has_nonterminating_errors : ScenariosBase + { + private PackageResult packageResult; + + public override void Context() + { + base.Context(); + Configuration.PackageNames = Configuration.Input = "nonterminatingerror"; + Configuration.Features.FailOnStandardError = false; //the default + + Scenario.add_packages_to_source_location(Configuration, Configuration.Input + "*" + Constants.PackageExtension); + } + + public override void Because() + { + Results = Service.install_run(Configuration); + packageResult = Results.FirstOrDefault().Value; + } + + [Fact] + public void should_install_where_install_location_reports() + { + Directory.Exists(packageResult.InstallLocation).ShouldBeTrue(); + } + + [Fact] + public void should_install_the_package_in_the_lib_directory() + { + var packageDir = Path.Combine(Scenario.get_top_level(), "lib", Configuration.Input); + + Directory.Exists(packageDir).ShouldBeTrue(); + } + + [Fact] + public void should_install_the_expected_version_of_the_package() + { + var packageFile = Path.Combine(Scenario.get_top_level(), "lib", Configuration.Input, Configuration.Input + Constants.PackageExtension); + var package = new OptimizedZipPackage(packageFile); + package.Version.Version.to_string().ShouldEqual("1.0.0.0"); + } + + [Fact] + public void should_contain_a_message_that_it_installed_successfully() + { + bool installedSuccessfully = false; + foreach (var message in MockLogger.MessagesFor(LogLevel.Warn).or_empty_list_if_null()) + { + if (message.Contains("1/1")) installedSuccessfully = true; + } + + installedSuccessfully.ShouldBeTrue(); + } + + [Fact] + public void should_have_a_successful_package_result() + { + packageResult.Success.ShouldBeTrue(); + } + + [Fact] + public void should_not_have_inconclusive_package_result() + { + packageResult.Inconclusive.ShouldBeFalse(); + } + + [Fact] + public void should_not_have_warning_package_result() + { + packageResult.Warning.ShouldBeFalse(); + } + + [Fact] + public void config_should_match_package_result_name() + { + packageResult.Name.ShouldEqual(Configuration.Input); + } + + [Fact] + public void should_have_a_version_of_one_dot_zero() + { + packageResult.Version.ShouldEqual("1.0"); + } + } + + + [Concern(typeof(ChocolateyInstallCommand))] + public class when_installing_a_package_that_has_nonterminating_errors_with_fail_on_stderr : ScenariosBase + { + private PackageResult packageResult; + + public override void Context() + { + base.Context(); + Configuration.PackageNames = Configuration.Input = "nonterminatingerror"; + Configuration.Features.FailOnStandardError = true; + + Scenario.add_packages_to_source_location(Configuration, Configuration.Input + "*" + Constants.PackageExtension); + } + + public override void Because() + { + Results = Service.install_run(Configuration); + packageResult = Results.FirstOrDefault().Value; + } + + [Fact] + public void should_not_install_a_package_in_the_lib_directory() + { + var packageDir = Path.Combine(Scenario.get_top_level(), "lib", Configuration.PackageNames); + + Directory.Exists(packageDir).ShouldBeFalse(); + } + + [Fact] + public void should_put_a_package_in_the_lib_bad_directory() + { + var packageDir = Path.Combine(Scenario.get_top_level(), "lib-bad", Configuration.PackageNames); + + Directory.Exists(packageDir).ShouldBeTrue(); + } + + [Fact] + public void should_contain_a_warning_message_that_it_was_unable_to_install_a_package() + { + bool installedSuccessfully = false; + foreach (var message in MockLogger.MessagesFor(LogLevel.Warn).or_empty_list_if_null()) + { + if (message.Contains("0/1")) installedSuccessfully = true; + } + + installedSuccessfully.ShouldBeTrue(); + } + + [Fact] + public void should_not_have_a_successful_package_result() + { + packageResult.Success.ShouldBeFalse(); + } + + [Fact] + public void should_not_have_inconclusive_package_result() + { + packageResult.Inconclusive.ShouldBeFalse(); + } + + [Fact] + public void should_not_have_warning_package_result() + { + packageResult.Warning.ShouldBeFalse(); + } + + [Fact] + public void should_have_an_error_package_result() + { + bool errorFound = false; + foreach (var message in packageResult.Messages) + { + if (message.MessageType == ResultType.Error) + { + errorFound = true; + } + } + + errorFound.ShouldBeTrue(); + } + + [Fact] + public void should_have_expected_error_in_package_result() + { + bool errorFound = false; + foreach (var message in packageResult.Messages) + { + if (message.MessageType == ResultType.Error) + { + if (message.Message.Contains("chocolateyInstall.ps1")) errorFound = true; + } + } + + errorFound.ShouldBeTrue(); + } + } + [Concern(typeof(ChocolateyInstallCommand))] public class when_installing_a_side_by_side_package : ScenariosBase { diff --git a/src/chocolatey/LogExtensions.cs b/src/chocolatey/LogExtensions.cs index f53a2fa5b8..e129b2d5ba 100644 --- a/src/chocolatey/LogExtensions.cs +++ b/src/chocolatey/LogExtensions.cs @@ -42,6 +42,14 @@ public static class LogExtensions // return Log(objectName); //} + /// + /// Resets the loggers. This allows switching to a new logger and not reusing old loggers that may be already cached. + /// + public static void ResetLoggers() + { + _dictionary.Value.Clear(); + } + /// /// Gets the logger for . /// diff --git a/src/chocolatey/infrastructure.app/ApplicationParameters.cs b/src/chocolatey/infrastructure.app/ApplicationParameters.cs index 755e6c2d1f..f5e22c73de 100644 --- a/src/chocolatey/infrastructure.app/ApplicationParameters.cs +++ b/src/chocolatey/infrastructure.app/ApplicationParameters.cs @@ -100,6 +100,7 @@ public static class Features public static readonly string AutoUninstaller = "autoUninstaller"; public static readonly string FailOnAutoUninstaller = "failOnAutoUninstaller"; public static readonly string AllowGlobalConfirmation = "allowGlobalConfirmation"; + public static readonly string FailOnStandardError = "failOnStandardError"; } public static class Messages diff --git a/src/chocolatey/infrastructure.app/builders/ConfigurationBuilder.cs b/src/chocolatey/infrastructure.app/builders/ConfigurationBuilder.cs index 0478b1552e..1bb651152c 100644 --- a/src/chocolatey/infrastructure.app/builders/ConfigurationBuilder.cs +++ b/src/chocolatey/infrastructure.app/builders/ConfigurationBuilder.cs @@ -187,6 +187,7 @@ private static void set_feature_flags(ChocolateyConfiguration config, ConfigFile config.Features.CheckSumFiles = set_feature_flag(ApplicationParameters.Features.CheckSumFiles, configFileSettings, defaultEnabled: true, description: "Checksum files when pulled in from internet (based on package)."); config.Features.AutoUninstaller = set_feature_flag(ApplicationParameters.Features.AutoUninstaller, configFileSettings, defaultEnabled: true, description: "Uninstall from programs and features without requiring an explicit uninstall script."); config.Features.FailOnAutoUninstaller = set_feature_flag(ApplicationParameters.Features.FailOnAutoUninstaller, configFileSettings, defaultEnabled: false, description: "Fail if automatic uninstaller fails."); + config.Features.FailOnStandardError = set_feature_flag(ApplicationParameters.Features.FailOnStandardError, configFileSettings, defaultEnabled: false, description: "Fail if install provider writes to stderr."); config.PromptForConfirmation = !set_feature_flag(ApplicationParameters.Features.AllowGlobalConfirmation, configFileSettings, defaultEnabled: false, description: "Prompt for confirmation in scripts or bypass."); } @@ -259,7 +260,10 @@ private static void set_global_options(IList args, ChocolateyConfigurati option => config.CacheLocation = option.remove_surrounding_quotes()) .Add("allowunofficial|allow-unofficial|allowunofficialbuild|allow-unofficial-build", "AllowUnofficialBuild - When not using the official build you must set this flag for choco to continue.", - option => config.AllowUnofficialBuild = option != null) + option => config.AllowUnofficialBuild = option != null) + .Add("failstderr|failonstderr|fail-on-stderr|fail-on-standard-error|fail-on-error-output", + "FailOnStandardError - Fail on standard error output (stderr), typically received when running external commands during install providers. This overrides the feature failOnStandardError.", + option => config.Features.FailOnStandardError = option != null) ; }, (unparsedArgs) => diff --git a/src/chocolatey/infrastructure.app/configuration/ChocolateyConfiguration.cs b/src/chocolatey/infrastructure.app/configuration/ChocolateyConfiguration.cs index 4012f90726..b1cc843e9c 100644 --- a/src/chocolatey/infrastructure.app/configuration/ChocolateyConfiguration.cs +++ b/src/chocolatey/infrastructure.app/configuration/ChocolateyConfiguration.cs @@ -335,6 +335,7 @@ public sealed class FeaturesConfiguration public bool AutoUninstaller { get; set; } public bool CheckSumFiles { get; set; } public bool FailOnAutoUninstaller { get; set; } + public bool FailOnStandardError { get; set; } } //todo: retrofit other command configs this way diff --git a/src/chocolatey/infrastructure.app/configuration/chocolatey.config b/src/chocolatey/infrastructure.app/configuration/chocolatey.config index 9cc93e4925..2a4275993d 100644 --- a/src/chocolatey/infrastructure.app/configuration/chocolatey.config +++ b/src/chocolatey/infrastructure.app/configuration/chocolatey.config @@ -16,5 +16,6 @@ + \ No newline at end of file diff --git a/src/chocolatey/infrastructure.app/services/PowershellService.cs b/src/chocolatey/infrastructure.app/services/PowershellService.cs index cd5f7853b5..90c66b93db 100644 --- a/src/chocolatey/infrastructure.app/services/PowershellService.cs +++ b/src/chocolatey/infrastructure.app/services/PowershellService.cs @@ -234,6 +234,7 @@ public bool run_action(ChocolateyConfiguration configuration, PackageResult pack if (shouldRun) { installerRun = true; + var errorMessagesLogged = false; var exitCode = PowershellExecutor.execute( wrap_script_with_module(chocoPowerShellScript, configuration), _fileSystem, @@ -268,7 +269,8 @@ public bool run_action(ChocolateyConfiguration configuration, PackageResult pack } else { - failure = true; + errorMessagesLogged = true; + if (configuration.Features.FailOnStandardError) failure = true; this.Log().Error(() => " " + e.Data); } }); @@ -278,6 +280,14 @@ public bool run_action(ChocolateyConfiguration configuration, PackageResult pack failure = true; } + if (!configuration.Features.FailOnStandardError && errorMessagesLogged) + { + this.Log().Warn(() => +@"Only an exit code of non-zero will fail the package by default. Set + `--failonstderr` if you want error messages to also fail a script. See + `choco -h` for details."); + } + if (failure) { Environment.ExitCode = exitCode; diff --git a/src/chocolatey/infrastructure.app/templates/ChocolateyInstallTemplate.cs b/src/chocolatey/infrastructure.app/templates/ChocolateyInstallTemplate.cs index 4ea5182448..cf4ffd38cc 100644 --- a/src/chocolatey/infrastructure.app/templates/ChocolateyInstallTemplate.cs +++ b/src/chocolatey/infrastructure.app/templates/ChocolateyInstallTemplate.cs @@ -41,7 +41,7 @@ public class ChocolateyInstallTemplate #file = $fileLocation #MSI - silentArgs = ""/qn /norestart /l*v `""$env:TEMP\chocolatey\$($packageName)\$($packageName).MsiInstall.log`"""" # ALLUSERS=1 DISABLEDESKTOPSHORTCUT=1 ADDDESKTOPICON=0 ADDSTARTMENU=0 + silentArgs = ""[[SilentArgs]]"" # ALLUSERS=1 DISABLEDESKTOPSHORTCUT=1 ADDDESKTOPICON=0 ADDSTARTMENU=0 validExitCodes= @(0, 3010, 1641) #OTHERS # Uncomment matching EXE type (sorted by most to least common) diff --git a/src/chocolatey/infrastructure.app/templates/TemplateValues.cs b/src/chocolatey/infrastructure.app/templates/TemplateValues.cs index e3a5b89c07..5ab827fa0e 100644 --- a/src/chocolatey/infrastructure.app/templates/TemplateValues.cs +++ b/src/chocolatey/infrastructure.app/templates/TemplateValues.cs @@ -32,7 +32,7 @@ public void set_normal() InstallerType = "EXE_MSI_OR_MSU"; Url = ""; Url64 = ""; - SilentArgs = ""; + SilentArgs = @"/qn /norestart /l*v `""$env:TEMP\chocolatey\$($packageName)\$($packageName).MsiInstall.log`"""; AutomaticPackageNotesNuspec = ""; Checksum = ""; ChecksumType = "md5"; diff --git a/src/chocolatey/infrastructure/logging/Log.cs b/src/chocolatey/infrastructure/logging/Log.cs index 72804f0621..a6572282d3 100644 --- a/src/chocolatey/infrastructure/logging/Log.cs +++ b/src/chocolatey/infrastructure/logging/Log.cs @@ -34,6 +34,7 @@ public static class Log public static void InitializeWith() where T : ILog, new() { _logType = typeof (T); + LogExtensions.ResetLoggers(); } /// @@ -45,6 +46,7 @@ public static void InitializeWith(ILog loggerType) { _logType = loggerType.GetType(); _logger = loggerType; + LogExtensions.ResetLoggers(); } ///