diff --git a/Directory.Packages.props b/Directory.Packages.props index bb6f569f4f9..da98047b43b 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -12,7 +12,6 @@ <_MicrosoftExtensionsPackageVersion>9.0.0 <_BasicReferenceAssembliesVersion>1.7.2 <_BenchmarkDotNetPackageVersion>0.13.5.2136 - <_MicrosoftVisualStudioExtensibilityTestingVersion>0.1.800-beta <_MicrosoftVisualStudioLanguageServicesPackageVersion>$(MicrosoftVisualStudioLanguageServicesPackageVersion) <_XunitPackageVersion>2.9.2 <_MicrosoftBuildPackageVersion>17.15.0-preview-25357-08 @@ -65,8 +64,8 @@ - - + + diff --git a/eng/Version.Details.props b/eng/Version.Details.props index 50bf16848db..e728fc18493 100644 --- a/eng/Version.Details.props +++ b/eng/Version.Details.props @@ -26,6 +26,8 @@ This file should be imported by eng/Versions.props 5.3.0-2.25630.5 5.3.0-2.25630.5 5.3.0-2.25630.5 + 5.3.0-2.25630.5 + 5.3.0-2.25630.5 5.3.0-2.25630.5 5.3.0-2.25630.5 @@ -59,6 +61,8 @@ This file should be imported by eng/Versions.props $(MicrosoftCodeAnalysisWorkspacesMSBuildPackageVersion) $(MicrosoftCommonLanguageServerProtocolFrameworkPackageVersion) $(MicrosoftNetCompilersToolsetPackageVersion) + $(MicrosoftVisualStudioExtensibilityTestingXunitPackageVersion) + $(MicrosoftVisualStudioExtensibilityTestingSourceGeneratorPackageVersion) $(MicrosoftVisualStudioLanguageServicesPackageVersion) $(RoslynDiagnosticsAnalyzersPackageVersion) diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index aaae815b9eb..d5d74ee8012 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -62,6 +62,14 @@ https://github.com/dotnet/roslyn 635d2b812121ef9fafe0de223b09be64e5a4a291 + + https://github.com/dotnet/roslyn + 635d2b812121ef9fafe0de223b09be64e5a4a291 + + + https://github.com/dotnet/roslyn + 635d2b812121ef9fafe0de223b09be64e5a4a291 + https://github.com/dotnet/roslyn 635d2b812121ef9fafe0de223b09be64e5a4a291 diff --git a/src/Razor/test/Microsoft.VisualStudio.Razor.IntegrationTests/InProcess/EditorInProcess_Caret.cs b/src/Razor/test/Microsoft.VisualStudio.Razor.IntegrationTests/InProcess/EditorInProcess_Caret.cs index b9434a10034..0194f09ae8e 100644 --- a/src/Razor/test/Microsoft.VisualStudio.Razor.IntegrationTests/InProcess/EditorInProcess_Caret.cs +++ b/src/Razor/test/Microsoft.VisualStudio.Razor.IntegrationTests/InProcess/EditorInProcess_Caret.cs @@ -13,26 +13,14 @@ namespace Microsoft.VisualStudio.Extensibility.Testing; internal partial class EditorInProcess { - public async Task MoveCaretAsync(int position, CancellationToken cancellationToken) - { - await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); - - var view = await GetActiveTextViewAsync(cancellationToken); - - var subjectBuffer = view.GetBufferContainingCaret(); - Assumes.Present(subjectBuffer); - - var point = new SnapshotPoint(subjectBuffer.CurrentSnapshot, position); - - view.Caret.MoveTo(point); - } - public async Task PlaceCaretAsync(int position, CancellationToken cancellationToken) { await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); var view = await GetActiveTextViewAsync(cancellationToken); view.Caret.MoveTo(new SnapshotPoint(view.GetBufferContainingCaret()!.CurrentSnapshot, position)); + + await ActivateAsync(cancellationToken); } public Task PlaceCaretAsync(string marker, int charsOffset, CancellationToken cancellationToken) diff --git a/src/Razor/test/Microsoft.VisualStudio.Razor.IntegrationTests/InProcess/EditorInProcess_Commands.cs b/src/Razor/test/Microsoft.VisualStudio.Razor.IntegrationTests/InProcess/EditorInProcess_Commands.cs index b6ba9a1c68a..dc443b2c582 100644 --- a/src/Razor/test/Microsoft.VisualStudio.Razor.IntegrationTests/InProcess/EditorInProcess_Commands.cs +++ b/src/Razor/test/Microsoft.VisualStudio.Razor.IntegrationTests/InProcess/EditorInProcess_Commands.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; +using Microsoft.AspNetCore.Razor.Threading; using Microsoft.VisualStudio.OLE.Interop; using Microsoft.VisualStudio.Razor.IntegrationTests.InProcess; using Microsoft.VisualStudio.Shell; @@ -56,13 +57,7 @@ public async Task InvokeRenameAsync(CancellationToken cancellationToken) { var commandGuid = typeof(VSStd2KCmdID).GUID; var commandId = VSStd2KCmdID.RENAME; - - // Rename seems to be extra-succeptable to COM exceptions - await Helper.RetryAsync(async (cancellationToken) => - { - await ExecuteCommandAsync(commandGuid, (uint)commandId, cancellationToken); - return true; - }, TimeSpan.FromSeconds(1), cancellationToken); + await ExecuteCommandAsync(commandGuid, (uint)commandId, cancellationToken); } public async Task CloseCodeFileAsync(string projectName, string relativeFilePath, bool saveFile, CancellationToken cancellationToken) @@ -88,7 +83,34 @@ private async Task ExecuteCommandAsync(Guid commandGuid, uint commandId, Cancell { await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); - var dispatcher = await GetRequiredGlobalServiceAsync(cancellationToken); + var dispatcher = await TestServices.Shell.GetRequiredGlobalServiceAsync(cancellationToken); + + // Before we execute the command, lets wait until it's enabled and available. Unfortunately this is an annoying COM pattern. + + // Set up the data for the API to fill in. We set command id, it sets the status in "cmdf" + var cmds = new OLECMD[1]; + cmds[0].cmdID = commandId; + cmds[0].cmdf = 0; + + await Helper.RetryAsync(ct => + { + // The return value here is just whether the QueryStatus call worked, not whether the command is enabled. + ErrorHandler.ThrowOnFailure(dispatcher.QueryStatus(ref commandGuid, 1, cmds, IntPtr.Zero)); + + // Now check the status flags that were filled in for the command we asked about. + var status = (OLECMDF)cmds[0].cmdf; + if (status.HasFlag(OLECMDF.OLECMDF_ENABLED) && + status.HasFlag(OLECMDF.OLECMDF_SUPPORTED)) + { + // Returning non-default from RetryAsync stops the retry loop. + return SpecializedTasks.True; + } + + // Returning default means it will try again. + return SpecializedTasks.False; + }, TimeSpan.FromMilliseconds(100), cancellationToken); + + // Now we can be reasonably sure the command is available, so execute it. ErrorHandler.ThrowOnFailure(dispatcher.Exec(commandGuid, commandId, (uint)OLECMDEXECOPT.OLECMDEXECOPT_DODEFAULT, IntPtr.Zero, IntPtr.Zero)); } diff --git a/src/Razor/test/Microsoft.VisualStudio.Razor.IntegrationTests/InProcess/ShellInProcess.cs b/src/Razor/test/Microsoft.VisualStudio.Razor.IntegrationTests/InProcess/ShellInProcess.cs index a29d90297e3..e81d8074e49 100644 --- a/src/Razor/test/Microsoft.VisualStudio.Razor.IntegrationTests/InProcess/ShellInProcess.cs +++ b/src/Razor/test/Microsoft.VisualStudio.Razor.IntegrationTests/InProcess/ShellInProcess.cs @@ -6,9 +6,11 @@ using System.IO; using System.Threading; using System.Threading.Tasks; +using Microsoft.VisualStudio.OperationProgress; using Microsoft.VisualStudio.Razor; using Microsoft.VisualStudio.Shell.Interop; using Microsoft.VisualStudio.TextManager.Interop; +using Microsoft.VisualStudio.Threading; using Xunit; namespace Microsoft.VisualStudio.Extensibility.Testing; @@ -110,4 +112,11 @@ internal async Task CloseActiveDocumentWindowsAsync(CancellationToken cancellati window.CloseFrame((uint)__FRAMECLOSE.FRAMECLOSE_NoSave); } } + + public async Task WaitForOperationProgressAsync(CancellationToken cancellationToken) + { + var operationProgressStatus = await GetRequiredGlobalServiceAsync(cancellationToken); + var stageStatus = operationProgressStatus.GetStageStatus(CommonOperationProgressStageIds.Intellisense); + await stageStatus.WaitForCompletionAsync().WithCancellation(cancellationToken); + } } diff --git a/src/Razor/test/Microsoft.VisualStudio.Razor.IntegrationTests/RazorCodeActionsTests.cs b/src/Razor/test/Microsoft.VisualStudio.Razor.IntegrationTests/RazorCodeActionsTests.cs index 3a1dcab5399..40ad2ee4acf 100644 --- a/src/Razor/test/Microsoft.VisualStudio.Razor.IntegrationTests/RazorCodeActionsTests.cs +++ b/src/Razor/test/Microsoft.VisualStudio.Razor.IntegrationTests/RazorCodeActionsTests.cs @@ -20,8 +20,8 @@ public async Task RazorCodeActions_AddUsing() // Open the file await TestServices.SolutionExplorer.OpenFileAsync(RazorProjectConstants.BlazorProjectName, RazorProjectConstants.CounterRazorFile, ControlledHangMitigatingCancellationToken); - await TestServices.Editor.SetTextAsync("", ControlledHangMitigatingCancellationToken); - await TestServices.Editor.MoveCaretAsync(3, ControlledHangMitigatingCancellationToken); + var position = await TestServices.Editor.SetTextAsync("", ControlledHangMitigatingCancellationToken); + await TestServices.Editor.PlaceCaretAsync(position, ControlledHangMitigatingCancellationToken); // Act var codeActions = await TestServices.Editor.InvokeCodeActionListAsync(ControlledHangMitigatingCancellationToken); diff --git a/src/Razor/test/Microsoft.VisualStudio.Razor.IntegrationTests/RenameTests.cs b/src/Razor/test/Microsoft.VisualStudio.Razor.IntegrationTests/RenameTests.cs index 3ef2044aa5b..87c927388b0 100644 --- a/src/Razor/test/Microsoft.VisualStudio.Razor.IntegrationTests/RenameTests.cs +++ b/src/Razor/test/Microsoft.VisualStudio.Razor.IntegrationTests/RenameTests.cs @@ -1,7 +1,12 @@ // 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.Threading; using System.Threading.Tasks; +using Microsoft.VisualStudio.Razor.IntegrationTests.Extensions; +using Microsoft.VisualStudio.Razor.IntegrationTests.InProcess; +using Microsoft.VisualStudio.Text.Editor.Commanding.Commands; using Xunit; using Xunit.Abstractions; @@ -212,21 +217,23 @@ await TestServices.SolutionExplorer.AddFileAsync(RazorProjectConstants.BlazorPro await TestServices.Editor.PlaceCaretAsync(position, ControlledHangMitigatingCancellationToken); - await Task.Delay(500); + // For some reason, this particular rename exercise is particularly flaky so we have a few hail marys to recite + await TestServices.Shell.WaitForOperationProgressAsync(ControlledHangMitigatingCancellationToken); + await WaitForRoslynRenameReadyAsync(ControlledHangMitigatingCancellationToken); // Act await TestServices.Editor.InvokeRenameAsync(ControlledHangMitigatingCancellationToken); + + // Even though we waited for the command to be ready and available, it can still be a bit slow to come up + await Task.Delay(500); + TestServices.Input.Send("ZooperDooper{ENTER}"); await TestServices.Editor.WaitForCurrentLineTextAsync("public class ZooperDooper : ComponentBase", ControlledHangMitigatingCancellationToken); - // The rename operation updates the editor as the new name is being typed, so waiting for the line in the editor can trigger before the rename - // actually occurs, and then moving tabs cancels it. So we have to wait a beat. - await Task.Delay(500); - // Assert await TestServices.SolutionExplorer.OpenFileAsync(RazorProjectConstants.BlazorProjectName, "MyPage.razor", ControlledHangMitigatingCancellationToken); - await TestServices.Editor.VerifyTextContainsAsync("", ControlledHangMitigatingCancellationToken); + await TestServices.Editor.WaitForTextContainsAsync("", ControlledHangMitigatingCancellationToken); } [IdeFact] @@ -351,4 +358,23 @@ public class MyComponent : ComponentBase await TestServices.SolutionExplorer.OpenFileAsync(RazorProjectConstants.BlazorProjectName, "MyComponent.cs", ControlledHangMitigatingCancellationToken); await TestServices.Editor.VerifyTextContainsAsync("public class ZooperDooper : ComponentBase", ControlledHangMitigatingCancellationToken); } + + private async Task WaitForRoslynRenameReadyAsync(CancellationToken cancellationToken) + { + await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + + var view = await TestServices.Editor.GetActiveTextViewAsync(cancellationToken); + var buffer = view.GetBufferContainingCaret(); + + var commandArgs = new RenameCommandArgs(view, buffer); + + // We don't have EA from this project, so we have to resort to reflection. Fortunately it's pretty simple + var roslynHandler = Type.GetType("Microsoft.CodeAnalysis.Editor.Implementation.InlineRename.AbstractRenameCommandHandler, Microsoft.CodeAnalysis.EditorFeatures"); + var canRename = roslynHandler.GetMethod("CanRename", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic); + + await Helper.RetryAsync(ct => + { + return Task.FromResult((bool)canRename.Invoke(null, [commandArgs])); + }, TimeSpan.FromMilliseconds(100), cancellationToken); + } }