Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,12 @@ public string InstallAndPurgeSnapshots(Func<PowerShell> pwshFactory, ILogger log

using (var pwsh = pwshFactory())
{
_installer.InstallSnapshot(_dependencyManifest, nextSnapshotPath, pwsh, logger);
_installer.InstallSnapshot(
_dependencyManifest,
nextSnapshotPath,
pwsh,
removeIfEquivalentToLatest: true,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please add a note why we set this to true? removeIfEquivalentToLatest: true

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you, added.

logger);
}

// Now that a new snapshot has been installed, there is a chance an old snapshot can be purged.
Expand Down
7 changes: 6 additions & 1 deletion src/DependencyManagement/DependencyManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,12 @@ private void InstallSnapshotInForeground(PowerShell pwsh, ILogger logger)
{
try
{
_installer.InstallSnapshot(_dependenciesFromManifest, _currentSnapshotPath, pwsh, logger);
_installer.InstallSnapshot(
_dependenciesFromManifest,
_currentSnapshotPath,
pwsh,
removeIfEquivalentToLatest: false,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please add a note why we set removeIfEquivalentToLatest: false?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

logger);
}
catch (Exception e)
{
Expand Down
58 changes: 38 additions & 20 deletions src/DependencyManagement/DependencySnapshotInstaller.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ public void InstallSnapshot(
IEnumerable<DependencyManifestEntry> dependencies,
string targetPath,
PowerShell pwsh,
bool removeIfEquivalentToLatest,
ILogger logger)
{
var installingPath = CreateInstallingSnapshot(targetPath);
Expand All @@ -56,30 +57,13 @@ public void InstallSnapshot(
InstallModule(module, installingPath, pwsh, logger);
}

var latestSnapshot = _storage.GetLatestInstalledSnapshot();
if (latestSnapshot != null && _snapshotComparer.AreEquivalent(installingPath, latestSnapshot, logger))
if (removeIfEquivalentToLatest)
{
logger.Log(
isUserOnlyLog: false,
LogLevel.Trace,
string.Format(PowerShellWorkerStrings.RemovingEquivalentDependencySnapshot, installingPath, latestSnapshot));

// The new snapshot is not better than the latest installed snapshot,
// so remove the new snapshot and update the timestamp of the latest snapshot
// in order to avoid unnecessary worker restarts.
_storage.RemoveSnapshot(installingPath);
_storage.SetSnapshotCreationTimeToUtcNow(latestSnapshot);
PromoteToInstalledOrRemove(installingPath, targetPath, logger);
}
else
{
_storage.PromoteInstallingSnapshotToInstalledAtomically(targetPath);

logger.Log(
isUserOnlyLog: false,
LogLevel.Trace,
string.Format(PowerShellWorkerStrings.PromotedDependencySnapshot, installingPath, targetPath));

_snapshotContentLogger.LogDependencySnapshotContent(targetPath, logger);
PromoteToInstalled(installingPath, targetPath, logger);
}
}
catch (Exception e)
Expand Down Expand Up @@ -146,6 +130,40 @@ private void InstallModule(DependencyInfo module, string installingPath, PowerSh
}
}

private void PromoteToInstalledOrRemove(string installingPath, string installedPath, ILogger logger)
{
var latestSnapshot = _storage.GetLatestInstalledSnapshot();
if (latestSnapshot != null && _snapshotComparer.AreEquivalent(installingPath, latestSnapshot, logger))
{
logger.Log(
isUserOnlyLog: false,
LogLevel.Trace,
string.Format(PowerShellWorkerStrings.RemovingEquivalentDependencySnapshot, installingPath, latestSnapshot));

// The new snapshot is not better than the latest installed snapshot,
// so remove the new snapshot and update the timestamp of the latest snapshot
// in order to avoid unnecessary worker restarts.
_storage.RemoveSnapshot(installingPath);
_storage.SetSnapshotCreationTimeToUtcNow(latestSnapshot);
}
else
{
PromoteToInstalled(installingPath, installedPath, logger);
}
}

private void PromoteToInstalled(string installingPath, string installedPath, ILogger logger)
{
_storage.PromoteInstallingSnapshotToInstalledAtomically(installedPath);

logger.Log(
isUserOnlyLog: false,
LogLevel.Trace,
string.Format(PowerShellWorkerStrings.PromotedDependencySnapshot, installingPath, installedPath));

_snapshotContentLogger.LogDependencySnapshotContent(installedPath, logger);
}

/// <summary>
/// Returns the string representation of the given attempt number.
/// </summary>
Expand Down
1 change: 1 addition & 0 deletions src/DependencyManagement/IDependencySnapshotInstaller.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ void InstallSnapshot(
IEnumerable<DependencyManifestEntry> dependencies,
string targetPath,
PowerShell pwsh,
bool removeIfEquivalentToLatest,
ILogger logger);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,11 @@ public void InstallsSnapshotIfNoRecentlyInstalledSnapshotFound()

_mockInstaller.Setup(
_ => _.InstallSnapshot(
It.IsAny<DependencyManifestEntry[]>(), It.IsAny<string>(), It.IsAny<PowerShell>(), It.IsAny<ILogger>()));
It.IsAny<DependencyManifestEntry[]>(),
It.IsAny<string>(),
It.IsAny<PowerShell>(),
It.IsAny<bool>(),
It.IsAny<ILogger>()));

using (var dummyPowerShell = PowerShell.Create())
using (var maintainer = CreateMaintainerWithMocks(_minBackgroundUpgradePeriod))
Expand All @@ -62,7 +66,7 @@ public void InstallsSnapshotIfNoRecentlyInstalledSnapshotFound()

// ReSharper disable once AccessToDisposedClosure
_mockInstaller.Verify(
_ => _.InstallSnapshot(_dependencyManifest, "new snapshot path", dummyPowerShell, _mockLogger.Object),
_ => _.InstallSnapshot(_dependencyManifest, "new snapshot path", dummyPowerShell, true, _mockLogger.Object),
Times.Once);

_mockPurger.Verify(_ => _.Purge(_mockLogger.Object), Times.Exactly(2));
Expand Down Expand Up @@ -116,7 +120,11 @@ public void LogsWarningIfCannotInstallSnapshot()

_mockInstaller.Setup(
_ => _.InstallSnapshot(
It.IsAny<DependencyManifestEntry[]>(), It.IsAny<string>(), It.IsAny<PowerShell>(), It.IsAny<ILogger>()))
It.IsAny<DependencyManifestEntry[]>(),
It.IsAny<string>(),
It.IsAny<PowerShell>(),
It.IsAny<bool>(),
It.IsAny<ILogger>()))
.Throws(injectedException);

using (var dummyPowerShell = PowerShell.Create())
Expand Down
14 changes: 12 additions & 2 deletions test/Unit/DependencyManagement/DependencyManagerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ public void StartDependencyInstallationIfNeeded_InstallsSnapshotInForeground_Whe
"NewSnapshot",
// Must run on the same runspace
It.Is<PowerShell>(powerShell => ReferenceEquals(firstPowerShellRunspace, powerShell)),
false, // removeIfEquivalentToLatest
_mockLogger.Object));

_mockStorage.Setup(_ => _.SnapshotExists("NewSnapshot")).Returns(false);
Expand Down Expand Up @@ -201,6 +202,7 @@ public void StartDependencyInstallationIfNeeded_InstallsSnapshotInForeground_Whe
"NewSnapshot",
// Must run on the same runspace
It.Is<PowerShell>(powerShell => ReferenceEquals(firstPowerShellRunspace, powerShell)),
false, // removeIfEquivalentToLatest
_mockLogger.Object));

_mockStorage.Setup(_ => _.SnapshotExists("NewSnapshot")).Returns(false);
Expand Down Expand Up @@ -239,7 +241,11 @@ public void StartDependencyInstallationIfNeeded_HandlesExceptionThrownBy_Install

_mockInstaller.Setup(
_ => _.InstallSnapshot(
It.IsAny<IEnumerable<DependencyManifestEntry>>(), It.IsAny<string>(), It.IsAny<PowerShell>(), It.IsAny<ILogger>()))
It.IsAny<IEnumerable<DependencyManifestEntry>>(),
It.IsAny<string>(),
It.IsAny<PowerShell>(),
It.IsAny<bool>(),
It.IsAny<ILogger>()))
.Throws(injectedException);

_mockStorage.Setup(_ => _.SnapshotExists(dependenciesPath)).Returns(false);
Expand All @@ -264,7 +270,11 @@ private void VerifyExactlyOneSnapshotInstalled()
{
_mockInstaller.Verify(
_ => _.InstallSnapshot(
It.IsAny<IEnumerable<DependencyManifestEntry>>(), It.IsAny<string>(), It.IsAny<PowerShell>(), It.IsAny<ILogger>()),
It.IsAny<IEnumerable<DependencyManifestEntry>>(),
It.IsAny<string>(),
It.IsAny<PowerShell>(),
It.IsAny<bool>(),
It.IsAny<ILogger>()),
Times.Once());

_mockInstaller.VerifyNoOtherCalls();
Expand Down
75 changes: 53 additions & 22 deletions test/Unit/DependencyManagement/DependencySnapshotInstallerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,16 @@ public void DoesNothingOnConstruction()
_mockLogger.VerifyNoOtherCalls();
}

[Fact]
public void SavesSpecifiedVersion_WhenExactVersionIsSpecified()
[Theory]
[InlineData(true)]
[InlineData(false)]
public void SavesSpecifiedVersion_WhenExactVersionIsSpecified(bool removeIfEquivalentToLatest)
{
var manifestEntries =
new[] { new DependencyManifestEntry("Module", VersionSpecificationType.ExactVersion, "Exact version") };

var installer = CreateDependenciesSnapshotInstallerWithMocks();
installer.InstallSnapshot(manifestEntries, _targetPathInstalled, PowerShell.Create(), _mockLogger.Object);
installer.InstallSnapshot(manifestEntries, _targetPathInstalled, PowerShell.Create(), removeIfEquivalentToLatest, _mockLogger.Object);

_mockModuleProvider.Verify(
_ => _.SaveModule(It.IsAny<PowerShell>(), "Module", "Exact version", _targetPathInstalling),
Expand All @@ -66,8 +68,10 @@ public void SavesSpecifiedVersion_WhenExactVersionIsSpecified()
Times.Never);
}

[Fact]
public void SavesLatestPublishedVersion_WhenMajorVersionIsSpecified()
[Theory]
[InlineData(true)]
[InlineData(false)]
public void SavesLatestPublishedVersion_WhenMajorVersionIsSpecified(bool removeIfEquivalentToLatest)
{
var manifestEntries =
new[] { new DependencyManifestEntry("Module", VersionSpecificationType.MajorVersion, "Major version") };
Expand All @@ -77,21 +81,23 @@ public void SavesLatestPublishedVersion_WhenMajorVersionIsSpecified()
.Returns("Latest version");

var installer = CreateDependenciesSnapshotInstallerWithMocks();
installer.InstallSnapshot(manifestEntries, _targetPathInstalled, PowerShell.Create(), _mockLogger.Object);
installer.InstallSnapshot(manifestEntries, _targetPathInstalled, PowerShell.Create(), removeIfEquivalentToLatest, _mockLogger.Object);

_mockModuleProvider.Verify(
_ => _.SaveModule(It.IsAny<PowerShell>(), "Module", "Latest version", _targetPathInstalling),
Times.Once);
}

[Fact]
public void PromotesInstallingSnapshotToInstalledIfSaveModuleDoesNotThrow()
[Theory]
[InlineData(true)]
[InlineData(false)]
public void PromotesInstallingSnapshotToInstalledIfSaveModuleDoesNotThrow(bool removeIfEquivalentToLatest)
{
var manifestEntries =
new[] { new DependencyManifestEntry("Module", VersionSpecificationType.ExactVersion, "Version") };

var installer = CreateDependenciesSnapshotInstallerWithMocks();
installer.InstallSnapshot(manifestEntries, _targetPathInstalled, PowerShell.Create(), _mockLogger.Object);
installer.InstallSnapshot(manifestEntries, _targetPathInstalled, PowerShell.Create(), removeIfEquivalentToLatest, _mockLogger.Object);

_mockStorage.Verify(_ => _.CreateInstallingSnapshot(_targetPathInstalled), Times.Once);
_mockStorage.Verify(_ => _.PromoteInstallingSnapshotToInstalledAtomically(_targetPathInstalled), Times.Once);
Expand All @@ -108,29 +114,50 @@ public void PromotesInstallingSnapshotToInstalledIfNotEquivalentToLatest()
_mockSnapshotComparer.Setup(_ => _.AreEquivalent(_targetPathInstalling, "snapshot", _mockLogger.Object)).Returns(false);

var installer = CreateDependenciesSnapshotInstallerWithMocks();
installer.InstallSnapshot(manifestEntries, _targetPathInstalled, PowerShell.Create(), _mockLogger.Object);
installer.InstallSnapshot(manifestEntries, _targetPathInstalled, PowerShell.Create(), removeIfEquivalentToLatest: true, _mockLogger.Object);

_mockStorage.Verify(_ => _.CreateInstallingSnapshot(_targetPathInstalled), Times.Once);
_mockStorage.Verify(_ => _.PromoteInstallingSnapshotToInstalledAtomically(_targetPathInstalled), Times.Once);
_mockSnapshotContentLogger.Verify(_ => _.LogDependencySnapshotContent(_targetPathInstalled, _mockLogger.Object), Times.Once);
}

[Fact]
public void CleansUpPowerShellRunspaceAfterSuccessfullySavingModule()
public void PromotesInstallingSnapshotToInstalledIfNotAskedToRemoveEquivalentSnapshots()
{
var manifestEntries =
new[] { new DependencyManifestEntry("Module", VersionSpecificationType.ExactVersion, "Version") };

_mockStorage.Setup(_ => _.GetLatestInstalledSnapshot()).Returns("snapshot");

var installer = CreateDependenciesSnapshotInstallerWithMocks();
installer.InstallSnapshot(manifestEntries, _targetPathInstalled, PowerShell.Create(), removeIfEquivalentToLatest: false, _mockLogger.Object);

_mockSnapshotComparer.VerifyNoOtherCalls();
_mockStorage.Verify(_ => _.CreateInstallingSnapshot(_targetPathInstalled), Times.Once);
_mockStorage.Verify(_ => _.PromoteInstallingSnapshotToInstalledAtomically(_targetPathInstalled), Times.Once);
_mockSnapshotContentLogger.Verify(_ => _.LogDependencySnapshotContent(_targetPathInstalled, _mockLogger.Object), Times.Once);
}

[Theory]
[InlineData(true)]
[InlineData(false)]
public void CleansUpPowerShellRunspaceAfterSuccessfullySavingModule(bool removeIfEquivalentToLatest)
{
var manifestEntries =
new[] { new DependencyManifestEntry("Module", VersionSpecificationType.ExactVersion, "Version") };

var dummyPowerShell = PowerShell.Create();

var installer = CreateDependenciesSnapshotInstallerWithMocks();
installer.InstallSnapshot(manifestEntries, _targetPathInstalled, dummyPowerShell, _mockLogger.Object);
installer.InstallSnapshot(manifestEntries, _targetPathInstalled, dummyPowerShell, removeIfEquivalentToLatest, _mockLogger.Object);

_mockModuleProvider.Verify(_ => _.Cleanup(dummyPowerShell), Times.Once);
}

[Fact]
public void LogsInstallationStartAndFinish()
[Theory]
[InlineData(true)]
[InlineData(false)]
public void LogsInstallationStartAndFinish(bool removeIfEquivalentToLatest)
{
var manifestEntries =
new[]
Expand All @@ -144,16 +171,18 @@ public void LogsInstallationStartAndFinish()
.Returns("Exact B version");

var installer = CreateDependenciesSnapshotInstallerWithMocks();
installer.InstallSnapshot(manifestEntries, _targetPathInstalled, PowerShell.Create(), _mockLogger.Object);
installer.InstallSnapshot(manifestEntries, _targetPathInstalled, PowerShell.Create(), removeIfEquivalentToLatest, _mockLogger.Object);

VerifyLoggedOnce(new[] { "Started installing", "A", "Exact A version" });
VerifyLoggedOnce(new[] { "has been installed", "A", "Exact A version" });
VerifyLoggedOnce(new[] { "Started installing", "B", "Exact B version" });
VerifyLoggedOnce(new[] { "has been installed", "B", "Exact B version" });
}

[Fact]
public void DoesNotSaveModuleIfGetLatestPublishedModuleVersionThrows()
[Theory]
[InlineData(true)]
[InlineData(false)]
public void DoesNotSaveModuleIfGetLatestPublishedModuleVersionThrows(bool removeIfEquivalentToLatest)
{
var manifestEntries =
new[] { new DependencyManifestEntry("Module", VersionSpecificationType.MajorVersion, "Version") };
Expand All @@ -172,7 +201,7 @@ public void DoesNotSaveModuleIfGetLatestPublishedModuleVersionThrows()

var installer = CreateDependenciesSnapshotInstallerWithMocks();
var caughtException = Assert.Throws<InvalidOperationException>(
() => installer.InstallSnapshot(manifestEntries, _targetPathInstalled, dummyPowerShell, _mockLogger.Object));
() => installer.InstallSnapshot(manifestEntries, _targetPathInstalled, dummyPowerShell, removeIfEquivalentToLatest, _mockLogger.Object));

Assert.Contains(injectedException.Message, caughtException.Message);

Expand All @@ -185,8 +214,10 @@ public void DoesNotSaveModuleIfGetLatestPublishedModuleVersionThrows()
_mockModuleProvider.Verify(_ => _.Cleanup(dummyPowerShell), Times.Once);
}

[Fact]
public void DoesNotPromoteSnapshotIfSaveModuleKeepsThrowing()
[Theory]
[InlineData(true)]
[InlineData(false)]
public void DoesNotPromoteSnapshotIfSaveModuleKeepsThrowing(bool removeIfEquivalentToLatest)
{
var manifestEntries =
new[] { new DependencyManifestEntry("Module", VersionSpecificationType.ExactVersion, "Version") };
Expand All @@ -203,7 +234,7 @@ public void DoesNotPromoteSnapshotIfSaveModuleKeepsThrowing()

var installer = CreateDependenciesSnapshotInstallerWithMocks();
var thrownException = Assert.Throws<DependencyInstallationException>(
() => installer.InstallSnapshot(manifestEntries, _targetPathInstalled, dummyPowerShell, _mockLogger.Object));
() => installer.InstallSnapshot(manifestEntries, _targetPathInstalled, dummyPowerShell, removeIfEquivalentToLatest, _mockLogger.Object));

Assert.Contains(injectedException.Message, thrownException.Message);

Expand All @@ -224,7 +255,7 @@ public void DoesNotPromoteSnapshotIfItIsEquivalentToLatest()
_mockStorage.Setup(_ => _.SetSnapshotCreationTimeToUtcNow("snapshot"));

var installer = CreateDependenciesSnapshotInstallerWithMocks();
installer.InstallSnapshot(manifestEntries, _targetPathInstalled, PowerShell.Create(), _mockLogger.Object);
installer.InstallSnapshot(manifestEntries, _targetPathInstalled, PowerShell.Create(), removeIfEquivalentToLatest: true, _mockLogger.Object);

_mockStorage.Verify(_ => _.PromoteInstallingSnapshotToInstalledAtomically(It.IsAny<string>()), Times.Never);
_mockStorage.Verify(_ => _.RemoveSnapshot(_targetPathInstalling), Times.Once);
Expand Down