Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Show workload update notifications for workloads installed via Visual Studio #40705

Merged
Merged
Show file tree
Hide file tree
Changes from all 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 @@ -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;
Expand Down Expand Up @@ -220,6 +221,16 @@ public IEnumerable<WorkloadId> GetUpdatableWorkloadsToAdvertise(IEnumerable<Work
{
try
{
#if !DOT_NET_BUILD_FROM_SOURCE
if (OperatingSystem.IsWindows())
{
// Also advertise updates for workloads installed by Visual Studio
InstalledWorkloadsCollection installedVSWorkloads = new InstalledWorkloadsCollection();
VisualStudioWorkloads.GetInstalledWorkloads(_workloadResolver, installedVSWorkloads, _sdkFeatureBand);
installedWorkloads = installedWorkloads.Concat(installedVSWorkloads.AsEnumerable().Select(kvp => 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);
Expand Down
37 changes: 37 additions & 0 deletions src/Tests/dotnet-MsiInstallation.Tests/Framework/VMStateTree.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,4 +77,41 @@ public VMStateTree ToVMStateTree()
return tree;
}
}

internal class VMState
{
public string DefaultRootState { get; set; }

public Dictionary<string, VMStateTree> VMStates { get; set; } = new Dictionary<string, VMStateTree>();

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<SerializableVMStateTree> VMStates { get; set; }

public VMState ToVMState()
{
return new VMState()
{
DefaultRootState = DefaultRootState,
VMStates = VMStates.Select(s => s.ToVMStateTree()).ToDictionary(s => s.SnapshotName)
};
}
}
}
32 changes: 30 additions & 2 deletions src/Tests/dotnet-MsiInstallation.Tests/Framework/VMTestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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}";
Copy link
Member

Choose a reason for hiding this comment

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

This path is only safe if someone is using an x64 SDK, so this won't work if you ever ran this under x64 emulation on arm64.

Copy link
Member Author

Choose a reason for hiding this comment

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

This is for the VM-based tests, which aren't currently targeting various architectures.


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();
Expand Down Expand Up @@ -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();
}
}
}
90 changes: 69 additions & 21 deletions src/Tests/dotnet-MsiInstallation.Tests/Framework/VirtualMachine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ class VirtualMachine : IDisposable

public VMTestSettings VMTestSettings { get; }

VMStateTree _rootState;
VMState _vmState;
VMStateTree _currentState;
VMStateTree _currentAppliedState;

Expand Down Expand Up @@ -81,38 +81,45 @@ public VirtualMachine(ITestOutputHelper log)
if (File.Exists(_stateFile))
{
string json = File.ReadAllText(_stateFile);
_rootState = JsonSerializer.Deserialize<SerializableVMStateTree>(json, GetSerializerOptions()).ToVMStateTree();
_vmState = JsonSerializer.Deserialize<SerializableVMState>(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();
Expand All @@ -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)
{
Expand All @@ -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());
Expand Down
29 changes: 28 additions & 1 deletion src/Tests/dotnet-MsiInstallation.Tests/MsiInstallerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ public void UpdateWithRollback()
InstallSdk();
InstallWorkload("wasm-tools");
ApplyRC1Manifests();

TestWasmWorkload();

// Second time applying same rollback file shouldn't do anything
Expand All @@ -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()
{
Expand Down
60 changes: 60 additions & 0 deletions src/Tests/dotnet-MsiInstallation.Tests/VSWorkloadTests.cs
Original file line number Diff line number Diff line change
@@ -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");
joeloff marked this conversation as resolved.
Show resolved Hide resolved
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");
}
}
}
20 changes: 0 additions & 20 deletions src/Tests/dotnet-MsiInstallation.Tests/WorkloadSetTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -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();
}
}
}