|
| 1 | +// ----------------------------------------------------------------------------- |
| 2 | +// <copyright file="DeltaSource.cs" company="Microsoft Corporation"> |
| 3 | +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. |
| 4 | +// </copyright> |
| 5 | +// ----------------------------------------------------------------------------- |
| 6 | + |
| 7 | +namespace AppInstallerCLIE2ETests |
| 8 | +{ |
| 9 | + using AppInstallerCLIE2ETests.Helpers; |
| 10 | + using NUnit.Framework; |
| 11 | + |
| 12 | + /// <summary> |
| 13 | + /// Tests the delta index against the deployed package mechanism. |
| 14 | + /// |
| 15 | + /// Unit tests cover the shape of a delta source and the merging of a delta with its baseline, |
| 16 | + /// but they run unpackaged, so they can only ever reach the local file store. Everything here |
| 17 | + /// is about the half they structurally cannot touch: deploying the packages, keeping three |
| 18 | + /// separate deployed identities apart, and the clean up that follows a removal. |
| 19 | + /// </summary> |
| 20 | + public class DeltaSource : BaseCommand |
| 21 | + { |
| 22 | + /// <summary> |
| 23 | + /// The experimental feature, as it is named in the settings file. |
| 24 | + /// </summary> |
| 25 | + private const string DeltaFeatureName = "deltaIndex"; |
| 26 | + |
| 27 | + /// <summary> |
| 28 | + /// A package that is in the baseline, and so is not one the delta has to supply. |
| 29 | + /// </summary> |
| 30 | + private const string BaselinePackageIdentifier = Constants.PortableExePackageId; |
| 31 | + |
| 32 | + /// <summary> |
| 33 | + /// One time set up. |
| 34 | + /// </summary> |
| 35 | + [OneTimeSetUp] |
| 36 | + public void OneTimeSetup() |
| 37 | + { |
| 38 | + WinGetSettingsHelper.ConfigureFeature(DeltaFeatureName, true); |
| 39 | + } |
| 40 | + |
| 41 | + /// <summary> |
| 42 | + /// One time tear down. |
| 43 | + /// </summary> |
| 44 | + [OneTimeTearDown] |
| 45 | + public void OneTimeTeardown() |
| 46 | + { |
| 47 | + RemoveDeltaSource(); |
| 48 | + WinGetSettingsHelper.ConfigureFeature(DeltaFeatureName, false); |
| 49 | + } |
| 50 | + |
| 51 | + /// <summary> |
| 52 | + /// Test set up. |
| 53 | + /// </summary> |
| 54 | + [SetUp] |
| 55 | + public void Setup() |
| 56 | + { |
| 57 | + RemoveDeltaSource(); |
| 58 | + WinGetSettingsHelper.ConfigureFeature(DeltaFeatureName, true); |
| 59 | + } |
| 60 | + |
| 61 | + /// <summary> |
| 62 | + /// Adding a delta capable source acquires the delta and the baseline rather than the full |
| 63 | + /// index, and what it then presents is the two of them merged. |
| 64 | + /// </summary> |
| 65 | + [Test] |
| 66 | + public void DeltaSourceAdd() |
| 67 | + { |
| 68 | + AddDeltaSource(); |
| 69 | + |
| 70 | + Assert.That(IsPackageDeployed(Constants.DeltaTestSourceDeltaIdentityName), Is.True, "The delta package should be deployed"); |
| 71 | + Assert.That( |
| 72 | + GetDeployedPackageVersion(Constants.DeltaTestSourceBaselineIdentityName), |
| 73 | + Is.EqualTo(Constants.DeltaTestSourceBaselineVersion), |
| 74 | + "The baseline should have been deployed at its own version"); |
| 75 | + |
| 76 | + // The baseline is a package of its own, so a source on the delta holds no full index at |
| 77 | + // all. Were the two to share an identity this could not be asked. |
| 78 | + Assert.That( |
| 79 | + IsPackageDeployed(Constants.DeltaTestSourceIdentityName), |
| 80 | + Is.False, |
| 81 | + "The full index should not have been acquired"); |
| 82 | + |
| 83 | + // This package was left out of the baseline, so it exists only in the delta. |
| 84 | + RequireFound(Constants.DeltaOnlyPackageIdentifier); |
| 85 | + |
| 86 | + // And a package that is in the baseline is still there, so the delta did not displace it. |
| 87 | + RequireFound(BaselinePackageIdentifier); |
| 88 | + } |
| 89 | + |
| 90 | + /// <summary> |
| 91 | + /// A source added while the feature was off holds the full index, and goes on holding it. |
| 92 | + /// |
| 93 | + /// Which packages a source is made of is established when it is added and recorded in its |
| 94 | + /// details; an update reads those details and never probes for anything else. So turning |
| 95 | + /// the feature on does not move an existing source onto the delta, and the source has to be |
| 96 | + /// re-added for that. This is deliberate -- see the no-migration decision -- and it is |
| 97 | + /// asserted here because nothing about it is obvious from the outside. |
| 98 | + /// </summary> |
| 99 | + [Test] |
| 100 | + public void DeltaSourceNotAdoptedByExistingFullIndexSource() |
| 101 | + { |
| 102 | + WinGetSettingsHelper.ConfigureFeature(DeltaFeatureName, false); |
| 103 | + |
| 104 | + AddDeltaSource(); |
| 105 | + |
| 106 | + Assert.That(IsPackageDeployed(Constants.DeltaTestSourceDeltaIdentityName), Is.False, "No delta should be involved while the feature is off"); |
| 107 | + Assert.That( |
| 108 | + GetDeployedPackageVersion(Constants.DeltaTestSourceIdentityName), |
| 109 | + Is.EqualTo(Constants.DeltaTestSourceVersion), |
| 110 | + "The full index should be deployed at its own version"); |
| 111 | + |
| 112 | + WinGetSettingsHelper.ConfigureFeature(DeltaFeatureName, true); |
| 113 | + |
| 114 | + var updateResult = TestCommon.RunAICLICommand("source update", $"-n {Constants.DeltaTestSourceName}"); |
| 115 | + Assert.That(updateResult.ExitCode, Is.EqualTo(Constants.ErrorCode.S_OK)); |
| 116 | + Assert.That(updateResult.StdOut, Does.Contain("Done")); |
| 117 | + |
| 118 | + Assert.That(IsPackageDeployed(Constants.DeltaTestSourceDeltaIdentityName), Is.False, "The update should not have acquired a delta"); |
| 119 | + Assert.That(IsPackageDeployed(Constants.DeltaTestSourceBaselineIdentityName), Is.False, "The update should not have acquired a baseline"); |
| 120 | + Assert.That( |
| 121 | + GetDeployedPackageVersion(Constants.DeltaTestSourceIdentityName), |
| 122 | + Is.EqualTo(Constants.DeltaTestSourceVersion), |
| 123 | + "The source should still be served by the full index"); |
| 124 | + |
| 125 | + // The full index holds everything, so what the source presents is unchanged. |
| 126 | + RequireFound(Constants.DeltaOnlyPackageIdentifier); |
| 127 | + RequireFound(BaselinePackageIdentifier); |
| 128 | + } |
| 129 | + |
| 130 | + /// <summary> |
| 131 | + /// Disabling delta after adding a source acquires the full index and keeps it usable. |
| 132 | + /// </summary> |
| 133 | + /// <param name="updateSource">Whether to explicitly update before searching.</param> |
| 134 | + [TestCase(false)] |
| 135 | + [TestCase(true)] |
| 136 | + public void DeltaSourceFeatureDisabled(bool updateSource) |
| 137 | + { |
| 138 | + AddDeltaSource(); |
| 139 | + |
| 140 | + Assert.That(IsPackageDeployed(Constants.DeltaTestSourceDeltaIdentityName), Is.True); |
| 141 | + Assert.That(IsPackageDeployed(Constants.DeltaTestSourceBaselineIdentityName), Is.True); |
| 142 | + Assert.That(IsPackageDeployed(Constants.DeltaTestSourceIdentityName), Is.False); |
| 143 | + RequireFound(Constants.DeltaOnlyPackageIdentifier); |
| 144 | + RequireFound(BaselinePackageIdentifier); |
| 145 | + |
| 146 | + WinGetSettingsHelper.ConfigureFeature(DeltaFeatureName, false); |
| 147 | + |
| 148 | + if (updateSource) |
| 149 | + { |
| 150 | + var updateResult = TestCommon.RunAICLICommand("source update", $"-n {Constants.DeltaTestSourceName}"); |
| 151 | + Assert.That(updateResult.ExitCode, Is.EqualTo(Constants.ErrorCode.S_OK)); |
| 152 | + Assert.That(updateResult.StdOut, Does.Contain("Done")); |
| 153 | + } |
| 154 | + |
| 155 | + RequireFound(Constants.DeltaOnlyPackageIdentifier); |
| 156 | + RequireFound(BaselinePackageIdentifier); |
| 157 | + Assert.That( |
| 158 | + GetDeployedPackageVersion(Constants.DeltaTestSourceIdentityName), |
| 159 | + Is.EqualTo(Constants.DeltaTestSourceVersion), |
| 160 | + "The source should be served by the full index with delta disabled"); |
| 161 | + |
| 162 | + var removeResult = TestCommon.RunAICLICommand("source remove", $"-n {Constants.DeltaTestSourceName}"); |
| 163 | + Assert.That(removeResult.ExitCode, Is.EqualTo(Constants.ErrorCode.S_OK)); |
| 164 | + Assert.That(IsPackageDeployed(Constants.DeltaTestSourceIdentityName), Is.False); |
| 165 | + Assert.That(IsPackageDeployed(Constants.DeltaTestSourceDeltaIdentityName), Is.False); |
| 166 | + Assert.That(IsPackageDeployed(Constants.DeltaTestSourceBaselineIdentityName), Is.False); |
| 167 | + } |
| 168 | + |
| 169 | + /// <summary> |
| 170 | + /// Removing the source takes both deployed packages with it. A delta source leaves more |
| 171 | + /// behind than an ordinary one, so there is more to get wrong. |
| 172 | + /// </summary> |
| 173 | + [Test] |
| 174 | + public void DeltaSourceRemove() |
| 175 | + { |
| 176 | + AddDeltaSource(); |
| 177 | + |
| 178 | + Assert.That(IsPackageDeployed(Constants.DeltaTestSourceDeltaIdentityName), Is.True); |
| 179 | + Assert.That(IsPackageDeployed(Constants.DeltaTestSourceBaselineIdentityName), Is.True); |
| 180 | + |
| 181 | + var removeResult = TestCommon.RunAICLICommand("source remove", $"-n {Constants.DeltaTestSourceName}"); |
| 182 | + Assert.That(removeResult.ExitCode, Is.EqualTo(Constants.ErrorCode.S_OK)); |
| 183 | + Assert.That(removeResult.StdOut, Does.Contain("Done")); |
| 184 | + |
| 185 | + Assert.That(IsPackageDeployed(Constants.DeltaTestSourceDeltaIdentityName), Is.False, "The delta package should have been removed"); |
| 186 | + Assert.That(IsPackageDeployed(Constants.DeltaTestSourceBaselineIdentityName), Is.False, "The baseline package should have been removed"); |
| 187 | + |
| 188 | + var listResult = TestCommon.RunAICLICommand("source list", $"-n {Constants.DeltaTestSourceName}"); |
| 189 | + Assert.That(listResult.ExitCode, Is.EqualTo(Constants.ErrorCode.ERROR_SOURCE_NAME_DOES_NOT_EXIST)); |
| 190 | + } |
| 191 | + |
| 192 | + /// <summary> |
| 193 | + /// Adds the delta source, requiring that it succeed. |
| 194 | + /// </summary> |
| 195 | + private static void AddDeltaSource() |
| 196 | + { |
| 197 | + var result = TestCommon.RunAICLICommand("source add", $"{Constants.DeltaTestSourceName} {Constants.DeltaTestSourceUrl} --trust-level trusted"); |
| 198 | + Assert.That(result.ExitCode, Is.EqualTo(Constants.ErrorCode.S_OK)); |
| 199 | + Assert.That(result.StdOut, Does.Contain("Done")); |
| 200 | + } |
| 201 | + |
| 202 | + /// <summary> |
| 203 | + /// Removes the delta source, tolerating its not having been added. |
| 204 | + /// </summary> |
| 205 | + private static void RemoveDeltaSource() |
| 206 | + { |
| 207 | + TestCommon.RunAICLICommand("source remove", $"-n {Constants.DeltaTestSourceName}"); |
| 208 | + } |
| 209 | + |
| 210 | + /// <summary> |
| 211 | + /// Requires that the delta source presents the given package. |
| 212 | + /// </summary> |
| 213 | + /// <param name="packageIdentifier">The package identifier.</param> |
| 214 | + private static void RequireFound(string packageIdentifier) |
| 215 | + { |
| 216 | + var result = TestCommon.RunAICLICommand("search", $"{packageIdentifier} --source {Constants.DeltaTestSourceName}"); |
| 217 | + Assert.That(result.ExitCode, Is.EqualTo(Constants.ErrorCode.S_OK), $"{packageIdentifier} should be found"); |
| 218 | + Assert.That(result.StdOut, Does.Contain(packageIdentifier)); |
| 219 | + } |
| 220 | + |
| 221 | + /// <summary> |
| 222 | + /// Whether a package with the given identity name is deployed for the current user. |
| 223 | + /// </summary> |
| 224 | + /// <param name="identityName">The package identity name.</param> |
| 225 | + /// <returns>True if it is deployed.</returns> |
| 226 | + private static bool IsPackageDeployed(string identityName) |
| 227 | + { |
| 228 | + return !string.IsNullOrEmpty(GetDeployedPackageVersion(identityName)); |
| 229 | + } |
| 230 | + |
| 231 | + /// <summary> |
| 232 | + /// Gets the version of a deployed package, or an empty string when it is not deployed. |
| 233 | + /// </summary> |
| 234 | + /// <param name="identityName">The package identity name.</param> |
| 235 | + /// <returns>The version, or an empty string.</returns> |
| 236 | + private static string GetDeployedPackageVersion(string identityName) |
| 237 | + { |
| 238 | + // The name has to match exactly, which matters here because the delta's identity name |
| 239 | + // has the full index's as a prefix. |
| 240 | + var result = TestCommon.RunCommandWithResult( |
| 241 | + "powershell", |
| 242 | + $"-Command \"(Get-AppxPackage | Where-Object {{ $_.Name -ceq '{identityName}' }} | Select-Object -First 1).Version\""); |
| 243 | + |
| 244 | + return result.StdOut.Trim(); |
| 245 | + } |
| 246 | + } |
| 247 | +} |
0 commit comments