Skip to content

Commit 3892cfb

Browse files
authored
Merge pull request chocolatey#2484 from TheCakeIsNaOH/reset-config
(chocolatey#1443) Reset config via action after every package upgrade
2 parents 097b52f + 8f15337 commit 3892cfb

File tree

6 files changed

+225
-20
lines changed

6 files changed

+225
-20
lines changed

Invoke-Tests.ps1

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,17 @@ try {
9393
Verbosity = 'Minimal'
9494
}
9595
Filter = @{
96-
ExcludeTag = 'Background', 'Licensed', 'CCM', 'WIP', 'NonAdmin', 'Internal'
96+
ExcludeTag = @(
97+
'Background'
98+
'Licensed'
99+
'CCM'
100+
'WIP'
101+
'NonAdmin'
102+
'Internal'
103+
if (-not $env:VM_RUNNING -and -not $env:TEST_KITCHEN) {
104+
'VMOnly'
105+
}
106+
)
97107
}
98108
}
99109

src/chocolatey/infrastructure.app/configuration/ChocolateyConfiguration.cs

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ namespace chocolatey.infrastructure.app.configuration
3030
[Serializable]
3131
public class ChocolateyConfiguration
3232
{
33+
[NonSerialized]
34+
private ChocolateyConfiguration _originalConfiguration;
35+
3336
public ChocolateyConfiguration()
3437
{
3538
RegularOutput = true;
@@ -58,6 +61,86 @@ public ChocolateyConfiguration()
5861
#endif
5962
}
6063

64+
/// <summary>
65+
/// Creates a backup of the current version of the configuration class.
66+
/// </summary>
67+
/// <exception cref="System.Runtime.Serialization.SerializationException">One or more objects in the class or child classes are not serializable.</exception>
68+
public void start_backup()
69+
{
70+
// We do this the easy way to ensure that we have a clean copy
71+
// of the original configuration file.
72+
_originalConfiguration = this.deep_copy();
73+
}
74+
75+
/// <summary>
76+
/// Restore the backup that has previously been created to the initial
77+
/// state, without making the class reference types the same to prevent
78+
/// the initial configuration class being updated at the same time if a
79+
/// value changes.
80+
/// </summary>
81+
/// <param name="removeBackup">Whether a backup that was previously made should be removed after resetting the configuration.</param>
82+
/// <exception cref="InvalidOperationException">No backup has been created before trying to reset the current configuration, and removal of the backup was not requested.</exception>
83+
/// <remarks>
84+
/// This call may make quite a lot of allocations on the Gen0 heap, as such
85+
/// it is best to keep the calls to this method at a minimum.
86+
/// </remarks>
87+
public void reset_config(bool removeBackup = false)
88+
{
89+
if (_originalConfiguration == null)
90+
{
91+
if (removeBackup)
92+
{
93+
// If we will also be removing the backup, we do not care if it is already
94+
// null or not, as that is the intended state when this method returns.
95+
return;
96+
}
97+
98+
throw new InvalidOperationException("No backup has been created before trying to reset the current configuration, and removal of the backup was not requested.");
99+
}
100+
101+
var t = this.GetType();
102+
103+
foreach (var property in t.GetProperties(BindingFlags.Public | BindingFlags.Instance))
104+
{
105+
try
106+
{
107+
var originalValue = property.GetValue(_originalConfiguration, new object[0]);
108+
109+
if (removeBackup || property.DeclaringType.IsPrimitive || property.DeclaringType.IsValueType || property.DeclaringType == typeof(string))
110+
{
111+
// If the property is a primitive, a value type or a string, then a copy of the value
112+
// will be created by the .NET Runtime automatically, and we do not need to create a deep clone of the value.
113+
// Additionally, if we will be removing the backup there is no need to create a deep copy
114+
// for any reference types, as such we also set the reference itself so it is not needed
115+
// to allocate more memory.
116+
property.SetValue(this, originalValue, new object[0]);
117+
}
118+
else if (originalValue != null)
119+
{
120+
// We need to do a deep copy of the value so it won't copy the reference itself,
121+
// but rather the actual values we are interested in.
122+
property.SetValue(this, originalValue.deep_copy(), new object[0]);
123+
}
124+
else
125+
{
126+
property.SetValue(this, null, new object[0]);
127+
}
128+
}
129+
catch (Exception ex)
130+
{
131+
throw new ApplicationException("Unable to restore the value for the property '{0}'.".format_with(property.Name), ex);
132+
}
133+
}
134+
135+
if (removeBackup)
136+
{
137+
// It is enough to set the original configuration to null to
138+
// allow GC to clean it up the next time it runs on the stored Generation
139+
// Heap Table.
140+
_originalConfiguration = null;
141+
}
142+
}
143+
61144
// overrides
62145
public override string ToString()
63146
{
@@ -237,6 +320,7 @@ private void append_output(StringBuilder propertyValues, string append)
237320

238321
[Obsolete("Side by Side installation is deprecated, and is pending removal in v2.0.0")]
239322
public bool AllowMultipleVersions { get; set; }
323+
240324
public bool AllowDowngrade { get; set; }
241325
public bool ForceDependencies { get; set; }
242326
public string DownloadChecksum { get; set; }

src/chocolatey/infrastructure.app/services/ChocolateyPackageService.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ namespace chocolatey.infrastructure.app.services
2222
using System.IO;
2323
using System.Linq;
2424
using System.Text;
25-
using builders;
2625
using commandline;
2726
using configuration;
2827
using domain;
@@ -31,8 +30,8 @@ namespace chocolatey.infrastructure.app.services
3130
using infrastructure.events;
3231
using infrastructure.services;
3332
using logging;
34-
using NuGet;
3533
using nuget;
34+
using NuGet;
3635
using platforms;
3736
using results;
3837
using tolerance;
@@ -91,6 +90,7 @@ system admins into something amazing! Singlehandedly solve your
9190
organization's struggles with software management and save the day!
9291
https://chocolatey.org/compare"
9392
};
93+
9494
private const string PRO_BUSINESS_LIST_MESSAGE = @"
9595
Did you know Pro / Business automatically syncs with Programs and
9696
Features? Learn more about Package Synchronizer at
@@ -742,7 +742,7 @@ private IEnumerable<ChocolateyConfiguration> get_packages_from_config(string pac
742742
if (pkgSettings.IgnoreDependencies) packageConfig.IgnoreDependencies = true;
743743
if (pkgSettings.ApplyInstallArgumentsToDependencies) packageConfig.ApplyInstallArgumentsToDependencies = true;
744744
if (pkgSettings.ApplyPackageParametersToDependencies) packageConfig.ApplyPackageParametersToDependencies = true;
745-
745+
746746
if (!string.IsNullOrWhiteSpace(pkgSettings.Source) && has_source_type(pkgSettings.Source))
747747
{
748748
packageConfig.SourceType = pkgSettings.Source;

src/chocolatey/infrastructure.app/services/NugetService.cs

Lines changed: 38 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -19,25 +19,24 @@ namespace chocolatey.infrastructure.app.services
1919
using System;
2020
using System.Collections.Concurrent;
2121
using System.Collections.Generic;
22-
using System.Globalization;
2322
using System.IO;
2423
using System.Linq;
2524
using System.Net;
26-
using NuGet;
2725
using adapters;
26+
using chocolatey.infrastructure.app.utility;
2827
using commandline;
2928
using configuration;
3029
using domain;
3130
using guards;
3231
using logging;
3332
using nuget;
33+
using NuGet;
3434
using platforms;
3535
using results;
3636
using tolerance;
3737
using DateTime = adapters.DateTime;
3838
using Environment = System.Environment;
3939
using IFileSystem = filesystem.IFileSystem;
40-
using chocolatey.infrastructure.app.utility;
4140

4241
//todo: #2575 - this monolith is too large. Refactor once test coverage is up.
4342

@@ -436,13 +435,13 @@ public virtual ConcurrentDictionary<string, PackageResult> install_run(Chocolate
436435
uninstallSuccessAction: null,
437436
addUninstallHandler: true);
438437

439-
var originalConfig = config.deep_copy();
438+
config.start_backup();
440439

441440
foreach (string packageName in packageNames.or_empty_list_if_null())
442441
{
443-
// reset config each time through
444-
config = originalConfig.deep_copy();
445-
442+
// We need to ensure we are using a clean configuration file
443+
// before we start reading it.
444+
config.reset_config();
446445
//todo: #2577 get smarter about realizing multiple versions have been installed before and allowing that
447446
IPackage installedPackage = packageManager.LocalRepository.FindPackage(packageName);
448447

@@ -555,6 +554,11 @@ Version was specified as '{0}'. It is possible that version
555554
}
556555
}
557556

557+
// Reset the configuration again once we are completely done with the processing of
558+
// configurations, and make sure that we are removing any backup that was created
559+
// as part of this run.
560+
config.reset_config(removeBackup: true);
561+
558562
return packageInstalls;
559563
}
560564

@@ -621,12 +625,13 @@ public virtual ConcurrentDictionary<string, PackageResult> upgrade_run(Chocolate
621625
set_package_names_if_all_is_specified(config, () => { config.IgnoreDependencies = true; });
622626
config.IgnoreDependencies = configIgnoreDependencies;
623627

624-
var originalConfig = config.deep_copy();
628+
config.start_backup();
625629

626630
foreach (string packageName in config.PackageNames.Split(new[] { ApplicationParameters.PackageNamesSeparator }, StringSplitOptions.RemoveEmptyEntries).or_empty_list_if_null())
627631
{
628-
// reset config each time through
629-
config = originalConfig.deep_copy();
632+
// We need to ensure we are using a clean configuration file
633+
// before we start reading it.
634+
config.reset_config();
630635

631636
IPackage installedPackage = packageManager.LocalRepository.FindPackage(packageName);
632637

@@ -878,6 +883,11 @@ public virtual ConcurrentDictionary<string, PackageResult> upgrade_run(Chocolate
878883
}
879884
}
880885

886+
// Reset the configuration again once we are completely done with the processing of
887+
// configurations, and make sure that we are removing any backup that was created
888+
// as part of this run.
889+
config.reset_config(removeBackup: true);
890+
881891
return packageInstalls;
882892
}
883893

@@ -897,12 +907,13 @@ public virtual ConcurrentDictionary<string, PackageResult> get_outdated(Chocolat
897907
set_package_names_if_all_is_specified(config, () => { config.IgnoreDependencies = true; });
898908
var packageNames = config.PackageNames.Split(new[] { ApplicationParameters.PackageNamesSeparator }, StringSplitOptions.RemoveEmptyEntries).or_empty_list_if_null().ToList();
899909

900-
var originalConfig = config.deep_copy();
910+
config.start_backup();
901911

902912
foreach (var packageName in packageNames)
903913
{
904-
// reset config each time through
905-
config = originalConfig.deep_copy();
914+
// We need to ensure we are using a clean configuration file
915+
// before we start reading it.
916+
config.reset_config();
906917

907918
var installedPackage = packageManager.LocalRepository.FindPackage(packageName);
908919
var pkgInfo = _packageInfoService.get_package_information(installedPackage);
@@ -961,6 +972,11 @@ public virtual ConcurrentDictionary<string, PackageResult> get_outdated(Chocolat
961972
}
962973
}
963974

975+
// Reset the configuration again once we are completely done with the processing of
976+
// configurations, and make sure that we are removing any backup that was created
977+
// as part of this run.
978+
config.reset_config(removeBackup: true);
979+
964980
return outdatedPackages;
965981
}
966982

@@ -1374,12 +1390,13 @@ public virtual ConcurrentDictionary<string, PackageResult> uninstall_run(Chocola
13741390
config.ForceDependencies = false;
13751391
});
13761392

1377-
var originalConfig = config.deep_copy();
1393+
config.start_backup();
13781394

13791395
foreach (string packageName in config.PackageNames.Split(new[] { ApplicationParameters.PackageNamesSeparator }, StringSplitOptions.RemoveEmptyEntries).or_empty_list_if_null())
13801396
{
1381-
// reset config each time through
1382-
config = originalConfig.deep_copy();
1397+
// We need to ensure we are using a clean configuration file
1398+
// before we start reading it.
1399+
config.reset_config();
13831400

13841401
IList<IPackage> installedPackageVersions = new List<IPackage>();
13851402
if (string.IsNullOrWhiteSpace(config.Version))
@@ -1507,6 +1524,11 @@ public virtual ConcurrentDictionary<string, PackageResult> uninstall_run(Chocola
15071524
}
15081525
}
15091526

1527+
// Reset the configuration again once we are completely done with the processing of
1528+
// configurations, and make sure that we are removing any backup that was created
1529+
// as part of this run.
1530+
config.reset_config(removeBackup: true);
1531+
15101532
return packageUninstalls;
15111533
}
15121534

tests/Vagrantfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ Vagrant.configure("2") do |config|
6060
6161
Write-Host "Build complete. Executing tests."
6262
# $env:TEST_KITCHEN = 1
63+
$env:VM_RUNNING = 1
6364
./Invoke-Tests.ps1
6465
SHELL
6566
end

0 commit comments

Comments
 (0)