diff --git a/src/Cli/dotnet/commands/dotnet-workload/install/WorkloadManifestUpdater.cs b/src/Cli/dotnet/commands/dotnet-workload/install/WorkloadManifestUpdater.cs index 4c60548cebb4..7dec00d46be5 100644 --- a/src/Cli/dotnet/commands/dotnet-workload/install/WorkloadManifestUpdater.cs +++ b/src/Cli/dotnet/commands/dotnet-workload/install/WorkloadManifestUpdater.cs @@ -8,6 +8,7 @@ using Microsoft.DotNet.Configurer; using Microsoft.DotNet.ToolPackage; using Microsoft.DotNet.Workloads.Workload.Install.InstallRecord; +using Microsoft.DotNet.Workloads.Workload.List; using Microsoft.Extensions.EnvironmentAbstractions; using Microsoft.NET.Sdk.WorkloadManifestReader; using NuGet.Common; @@ -220,6 +221,16 @@ public IEnumerable GetUpdatableWorkloadsToAdvertise(IEnumerable new WorkloadId(kvp.Key))).Distinct().ToList(); + } +#endif + var overlayProvider = new TempDirectoryWorkloadManifestProvider(Path.Combine(_userProfileDir, "sdk-advertising", _sdkFeatureBand.ToString()), _sdkFeatureBand.ToString()); var advertisingManifestResolver = _workloadResolver.CreateOverlayResolver(overlayProvider); return _workloadResolver.GetUpdatedWorkloads(advertisingManifestResolver, installedWorkloads); diff --git a/src/Tests/dotnet-MsiInstallation.Tests/Framework/VMStateTree.cs b/src/Tests/dotnet-MsiInstallation.Tests/Framework/VMStateTree.cs index aba8555d4e6c..ca5d69017f64 100644 --- a/src/Tests/dotnet-MsiInstallation.Tests/Framework/VMStateTree.cs +++ b/src/Tests/dotnet-MsiInstallation.Tests/Framework/VMStateTree.cs @@ -77,4 +77,41 @@ public VMStateTree ToVMStateTree() return tree; } } + + internal class VMState + { + public string DefaultRootState { get; set; } + + public Dictionary VMStates { get; set; } = new Dictionary(); + + public SerializableVMState ToSerializable() + { + return new SerializableVMState() + { + DefaultRootState = DefaultRootState, + VMStates = VMStates.Values.Select(v => v.ToSerializeable()).ToList() + }; + } + + public VMStateTree GetRootState() + { + return VMStates[DefaultRootState]; + } + } + + internal class SerializableVMState + { + public string DefaultRootState { get; set; } + + public List VMStates { get; set; } + + public VMState ToVMState() + { + return new VMState() + { + DefaultRootState = DefaultRootState, + VMStates = VMStates.Select(s => s.ToVMStateTree()).ToDictionary(s => s.SnapshotName) + }; + } + } } diff --git a/src/Tests/dotnet-MsiInstallation.Tests/Framework/VMTestBase.cs b/src/Tests/dotnet-MsiInstallation.Tests/Framework/VMTestBase.cs index 769184741793..d2cebfc0f54a 100644 --- a/src/Tests/dotnet-MsiInstallation.Tests/Framework/VMTestBase.cs +++ b/src/Tests/dotnet-MsiInstallation.Tests/Framework/VMTestBase.cs @@ -94,14 +94,22 @@ protected void DeployStage2Sdk() return; } - var installedSdkFolder = $@"c:\Program Files\dotnet\sdk\{SdkInstallerVersion}"; + var result = VM.CreateRunCommand("dotnet", "--version") + .WithIsReadOnly(true) + .Execute(); + + result.Should().Pass(); + + string existingVersionToOverwrite = result.StdOut; + + var installedSdkFolder = $@"c:\Program Files\dotnet\sdk\{existingVersionToOverwrite}"; Log.WriteLine($"Deploying SDK from {TestContext.Current.ToolsetUnderTest.SdkFolderUnderTest} to {installedSdkFolder} on VM."); // TODO: It would be nice if the description included the date/time of the SDK build, to distinguish different snapshots VM.CreateActionGroup("Deploy Stage 2 SDK", VM.CopyFolder(TestContext.Current.ToolsetUnderTest.SdkFolderUnderTest, installedSdkFolder), - ChangeVersionFileContents(SdkInstallerVersion)) + ChangeVersionFileContents(existingVersionToOverwrite)) .Execute() .Should() .Pass(); @@ -179,5 +187,25 @@ protected WorkloadSet ParseRollbackOutput(string output) return WorkloadSet.FromJson(filteredOutput, defaultFeatureBand: new SdkFeatureBand(SdkInstallerVersion)); } + + protected string GetWorkloadVersion() + { + var result = VM.CreateRunCommand("dotnet", "workload", "--version") + .WithIsReadOnly(true) + .Execute(); + + result.Should().Pass(); + + return result.StdOut; + } + + protected void AddNuGetSource(string source) + { + VM.CreateRunCommand("dotnet", "nuget", "add", "source", source) + .WithDescription($"Add {source} to NuGet.config") + .Execute() + .Should() + .Pass(); + } } } diff --git a/src/Tests/dotnet-MsiInstallation.Tests/Framework/VirtualMachine.cs b/src/Tests/dotnet-MsiInstallation.Tests/Framework/VirtualMachine.cs index 899f5a42db06..844a1802e7d7 100644 --- a/src/Tests/dotnet-MsiInstallation.Tests/Framework/VirtualMachine.cs +++ b/src/Tests/dotnet-MsiInstallation.Tests/Framework/VirtualMachine.cs @@ -16,7 +16,7 @@ class VirtualMachine : IDisposable public VMTestSettings VMTestSettings { get; } - VMStateTree _rootState; + VMState _vmState; VMStateTree _currentState; VMStateTree _currentAppliedState; @@ -81,38 +81,45 @@ public VirtualMachine(ITestOutputHelper log) if (File.Exists(_stateFile)) { string json = File.ReadAllText(_stateFile); - _rootState = JsonSerializer.Deserialize(json, GetSerializerOptions()).ToVMStateTree(); + _vmState = JsonSerializer.Deserialize(json, GetSerializerOptions()).ToVMState(); } else { - var snapshots = VMControl.GetSnapshots(); - var testStartSnapshots = snapshots.Where(s => s.name.Contains("Test start", StringComparison.OrdinalIgnoreCase)).ToList(); - if (testStartSnapshots.Count == 0) - { - throw new Exception("No test start snapshots found"); - } - else if (testStartSnapshots.Count > 1) + _vmState = new VMState(); + } + + // Determine test start state + var snapshots = VMControl.GetSnapshots(); + var testStartSnapshots = snapshots.Where(s => s.name.Contains("Test start", StringComparison.OrdinalIgnoreCase)).ToList(); + if (testStartSnapshots.Count == 0) + { + throw new Exception("No test start snapshots found"); + } + else if (testStartSnapshots.Count > 1) + { + foreach (var snapshot in testStartSnapshots) { - foreach (var snapshot in testStartSnapshots) - { - Log.WriteLine(snapshot.id + ": " + snapshot.name); - } - throw new Exception("Multiple test start snapshots found"); + Log.WriteLine(snapshot.id + ": " + snapshot.name); } - _rootState = new VMStateTree + throw new Exception("Multiple test start snapshots found"); + } + + _vmState.DefaultRootState = testStartSnapshots[0].name; + if (!_vmState.VMStates.ContainsKey(_vmState.DefaultRootState)) + { + _vmState.VMStates[_vmState.DefaultRootState] = new VMStateTree() { - SnapshotId = testStartSnapshots[0].Item1, - SnapshotName = testStartSnapshots[0].Item2 + SnapshotId = testStartSnapshots[0].id, + SnapshotName = testStartSnapshots[0].name }; } - - _currentState = _rootState; + _currentState = _vmState.GetRootState(); TrimMissingSnapshots(); } public void Dispose() { - string json = JsonSerializer.Serialize(_rootState.ToSerializeable(), GetSerializerOptions()); + string json = JsonSerializer.Serialize(_vmState.ToSerializable(), GetSerializerOptions()); File.WriteAllText(_stateFile, json); VMControl.Dispose(); @@ -137,7 +144,17 @@ public void TrimMissingSnapshots() { var snapshotIds = VMControl.GetSnapshots().Select(s => s.id).ToHashSet(); - Recurse(_rootState); + foreach (var state in _vmState.VMStates.Values.ToList()) + { + if (!snapshotIds.Contains(state.SnapshotId)) + { + _vmState.VMStates.Remove(state.SnapshotId); + } + else + { + Recurse(state); + } + } void Recurse(VMStateTree node) { @@ -155,6 +172,37 @@ void Recurse(VMStateTree node) } } + public void SetCurrentState(string stateName) + { + if (_vmState.VMStates.TryGetValue(stateName, out var state)) + { + _currentState = state; + } + else + { + var snapshots = VMControl.GetSnapshots(); + var matchingSnapshots = snapshots.Where(s => s.name.Equals(stateName, StringComparison.OrdinalIgnoreCase)).ToList(); + if (matchingSnapshots.Count == 0) + { + throw new Exception($"No snapshot found with name {stateName}"); + } + else if (matchingSnapshots.Count > 1) + { + throw new Exception($"Multiple snapshots found with name {stateName}"); + } + else + { + var newState = new VMStateTree() + { + SnapshotId = matchingSnapshots[0].id, + SnapshotName = matchingSnapshots[0].name, + }; + _vmState.VMStates[stateName] = newState; + _currentState = newState; + } + } + } + public VMRunAction CreateRunCommand(params string[] args) { return new VMRunAction(this, args.ToList()); diff --git a/src/Tests/dotnet-MsiInstallation.Tests/MsiInstallerTests.cs b/src/Tests/dotnet-MsiInstallation.Tests/MsiInstallerTests.cs index 06f4ded4129f..2b4129f6db41 100644 --- a/src/Tests/dotnet-MsiInstallation.Tests/MsiInstallerTests.cs +++ b/src/Tests/dotnet-MsiInstallation.Tests/MsiInstallerTests.cs @@ -197,7 +197,7 @@ public void UpdateWithRollback() InstallSdk(); InstallWorkload("wasm-tools"); ApplyRC1Manifests(); - + TestWasmWorkload(); // Second time applying same rollback file shouldn't do anything @@ -220,6 +220,33 @@ public void InstallWithRollback() TestWasmWorkload(); } + [Fact] + public void InstallShouldNotUpdatePinnedRollback() + { + InstallSdk(); + ApplyRC1Manifests(); + var workloadVersion = GetWorkloadVersion(); + + InstallWorkload("aspire"); + + GetWorkloadVersion().Should().Be(workloadVersion); + } + + [Fact] + public void UpdateShouldUndoPinnedRollback() + { + InstallSdk(); + ApplyRC1Manifests(); + var workloadVersion = GetWorkloadVersion(); + + VM.CreateRunCommand("dotnet", "workload", "update") + .Execute() + .Should().Pass(); + + GetWorkloadVersion().Should().NotBe(workloadVersion); + + } + [Fact] public void ShouldNotShowRebootMessage() { diff --git a/src/Tests/dotnet-MsiInstallation.Tests/VSWorkloadTests.cs b/src/Tests/dotnet-MsiInstallation.Tests/VSWorkloadTests.cs new file mode 100644 index 000000000000..7d28064c8002 --- /dev/null +++ b/src/Tests/dotnet-MsiInstallation.Tests/VSWorkloadTests.cs @@ -0,0 +1,60 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.DotNet.MsiInstallerTests.Framework; + +namespace Microsoft.DotNet.MsiInstallerTests +{ + public class VSWorkloadTests : VMTestBase + { + public VSWorkloadTests(ITestOutputHelper log) : base(log) + { + VM.SetCurrentState("Install VS 17.10 Preview 6"); + DeployStage2Sdk(); + } + + [Fact] + public void WorkloadListShowsVSInstalledWorkloads() + { + var result = VM.CreateRunCommand("dotnet", "workload", "list") + .WithIsReadOnly(true) + .Execute(); + + result.Should().Pass(); + + result.Should().HaveStdOutContaining("aspire"); + } + + [Fact] + public void UpdatesAreAdvertisedForVSInstalledWorkloads() + { + AddNuGetSource("https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet8/nuget/v3/index.json"); + + VM.CreateRunCommand("dotnet", "new", "classlib", "-o", "LibraryTest") + .WithWorkingDirectory(@"C:\SdkTesting") + .Execute() + .Should() + .Pass(); + + // build (or any restoring) command should check for and notify of updates + VM.CreateRunCommand("dotnet", "build") + .WithWorkingDirectory(@"C:\SdkTesting\LibraryTest") + .Execute().Should().Pass() + .And.HaveStdOutContaining("Workload updates are available"); + + // Workload list should list the specific workloads that have updates + VM.CreateRunCommand("dotnet", "workload", "list") + .WithIsReadOnly(true) + .Execute() + .Should() + .Pass() + .And + .HaveStdOutContaining("Updates are available for the following workload(s): aspire"); + } + } +} diff --git a/src/Tests/dotnet-MsiInstallation.Tests/WorkloadSetTests.cs b/src/Tests/dotnet-MsiInstallation.Tests/WorkloadSetTests.cs index 64a182f87493..63f6a565d57a 100644 --- a/src/Tests/dotnet-MsiInstallation.Tests/WorkloadSetTests.cs +++ b/src/Tests/dotnet-MsiInstallation.Tests/WorkloadSetTests.cs @@ -213,17 +213,6 @@ public void UpdateToWorkloadSetVersionWithManifestsNotAvailable() GetWorkloadVersion().Should().Be(workloadVersionBeforeUpdate); } - string GetWorkloadVersion() - { - var result = VM.CreateRunCommand("dotnet", "workload", "--version") - .WithIsReadOnly(true) - .Execute(); - - result.Should().Pass(); - - return result.StdOut; - } - string GetUpdateMode() { var result = VM.CreateRunCommand("dotnet", "workload", "config", "--update-mode") @@ -234,14 +223,5 @@ string GetUpdateMode() return result.StdOut; } - - void AddNuGetSource(string source) - { - VM.CreateRunCommand("dotnet", "nuget", "add", "source", source) - .WithDescription($"Add {source} to NuGet.config") - .Execute() - .Should() - .Pass(); - } } }