diff --git a/src/VisualStudio/IntegrationTest/New.IntegrationTests/CSharp/CSharpBuild.cs b/src/VisualStudio/IntegrationTest/New.IntegrationTests/CSharp/CSharpBuild.cs index 9134b15b0b21a..a3468dee04f89 100644 --- a/src/VisualStudio/IntegrationTest/New.IntegrationTests/CSharp/CSharpBuild.cs +++ b/src/VisualStudio/IntegrationTest/New.IntegrationTests/CSharp/CSharpBuild.cs @@ -42,7 +42,7 @@ static void Main(string[] args) await TestServices.Editor.SetTextAsync(editorText, HangMitigatingCancellationToken); - var buildSummary = await TestServices.SolutionExplorer.BuildSolutionAsync(waitForBuildToFinish: true, HangMitigatingCancellationToken); + var buildSummary = await TestServices.SolutionExplorer.BuildSolutionAndWaitAsync(HangMitigatingCancellationToken); Assert.Equal("========== Build: 1 succeeded, 0 failed, 0 up-to-date, 0 skipped ==========", buildSummary); await TestServices.ErrorList.ShowBuildErrorsAsync(HangMitigatingCancellationToken); diff --git a/src/VisualStudio/IntegrationTest/New.IntegrationTests/CSharp/CSharpErrorListCommon.cs b/src/VisualStudio/IntegrationTest/New.IntegrationTests/CSharp/CSharpErrorListCommon.cs index 6d267d1a55f69..63d2e9c153b13 100644 --- a/src/VisualStudio/IntegrationTest/New.IntegrationTests/CSharp/CSharpErrorListCommon.cs +++ b/src/VisualStudio/IntegrationTest/New.IntegrationTests/CSharp/CSharpErrorListCommon.cs @@ -52,7 +52,7 @@ static void Main(string[] args) var target = await TestServices.ErrorList.NavigateToErrorListItemAsync(0, isPreview: false, shouldActivate: true, HangMitigatingCancellationToken); Assert.Equal(expectedContents[0], target); Assert.Equal(25, await TestServices.Editor.GetCaretPositionAsync(HangMitigatingCancellationToken)); - await TestServices.SolutionExplorer.BuildSolutionAsync(waitForBuildToFinish: true, HangMitigatingCancellationToken); + await TestServices.SolutionExplorer.BuildSolutionAndWaitAsync(HangMitigatingCancellationToken); await TestServices.ErrorList.ShowErrorListAsync(HangMitigatingCancellationToken); await TestServices.Workspace.WaitForAllAsyncOperationsAsync(new[] { FeatureAttribute.Workspace, FeatureAttribute.SolutionCrawler, FeatureAttribute.DiagnosticService, FeatureAttribute.ErrorSquiggles, FeatureAttribute.ErrorList }, HangMitigatingCancellationToken); actualContents = await TestServices.ErrorList.GetErrorsAsync(HangMitigatingCancellationToken); diff --git a/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/SolutionExplorerInProcess.cs b/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/SolutionExplorerInProcess.cs index 3731514283cd0..c84ffc4006c57 100644 --- a/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/SolutionExplorerInProcess.cs +++ b/src/VisualStudio/IntegrationTest/New.IntegrationTests/InProcess/SolutionExplorerInProcess.cs @@ -511,49 +511,35 @@ public async Task GetFileContentsAsync(string projectName, string relati } /// - /// If is , returns the build status line, which generally looks something like this: + /// The summary line for the build, which generally looks something like this: /// /// /// ========== Build: 1 succeeded, 0 failed, 0 up-to-date, 0 skipped ========== /// - /// - /// Otherwise, this method does not wait for the build to complete and returns . /// - public async Task BuildSolutionAsync(bool waitForBuildToFinish, CancellationToken cancellationToken) + public async Task BuildSolutionAndWaitAsync(CancellationToken cancellationToken) { await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); var buildOutputWindowPane = await GetBuildOutputWindowPaneAsync(cancellationToken); buildOutputWindowPane.Clear(); - await TestServices.Shell.ExecuteCommandAsync(VSConstants.VSStd97CmdID.BuildSln, cancellationToken); - if (waitForBuildToFinish) - { - return await WaitForBuildToFinishAsync(buildOutputWindowPane, cancellationToken); - } - - return null; - } - - /// - public async Task WaitForBuildToFinishAsync(CancellationToken cancellationToken) - { - var buildOutputWindowPane = await GetBuildOutputWindowPaneAsync(cancellationToken); - return await WaitForBuildToFinishAsync(buildOutputWindowPane, cancellationToken); - } + var buildManager = await GetRequiredGlobalServiceAsync(cancellationToken); + using var solutionEvents = new UpdateSolutionEvents(buildManager); + var buildCompleteTaskCompletionSource = new TaskCompletionSource(); - /// - /// The summary line for the build, which generally looks something like this: - /// - /// - /// ========== Build: 1 succeeded, 0 failed, 0 up-to-date, 0 skipped ========== - /// - /// - private async Task WaitForBuildToFinishAsync(IVsOutputWindowPane buildOutputWindowPane, CancellationToken cancellationToken) - { - await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + void HandleUpdateSolutionDone() => buildCompleteTaskCompletionSource.SetResult(true); + solutionEvents.OnUpdateSolutionDone += HandleUpdateSolutionDone; + try + { + await TestServices.Shell.ExecuteCommandAsync(VSConstants.VSStd97CmdID.BuildSln, cancellationToken); - await KnownUIContexts.SolutionExistsAndNotBuildingAndNotDebuggingContext; + await buildCompleteTaskCompletionSource.Task; + } + finally + { + solutionEvents.OnUpdateSolutionDone -= HandleUpdateSolutionDone; + } // Force the error list to update ErrorHandler.ThrowOnFailure(buildOutputWindowPane.FlushToTaskList()); @@ -566,8 +552,17 @@ private async Task WaitForBuildToFinishAsync(IVsOutputWindowPane buildOu return string.Empty; } - // The build summary line should be second to last in the output window - return lines[^2].Extent.GetText(); + // Find the build summary line + for (var index = lines.Count - 1; index >= 0; index--) + { + var lineText = lines[index].Extent.GetText(); + if (lineText.StartsWith("========== Build:")) + { + return lineText; + } + } + + return string.Empty; } public async Task GetBuildOutputWindowPaneAsync(CancellationToken cancellationToken) @@ -682,4 +677,45 @@ private static string CreateTemporaryPath() }); } } + + internal sealed class UpdateSolutionEvents : IVsUpdateSolutionEvents, IDisposable + { + private uint _cookie; + private readonly IVsSolutionBuildManager2 _solutionBuildManager; + + public event Action? OnUpdateSolutionDone; + + internal UpdateSolutionEvents(IVsSolutionBuildManager2 solutionBuildManager) + { + ThreadHelper.ThrowIfNotOnUIThread(); + + _solutionBuildManager = solutionBuildManager; + ErrorHandler.ThrowOnFailure(solutionBuildManager.AdviseUpdateSolutionEvents(this, out _cookie)); + } + + int IVsUpdateSolutionEvents.UpdateSolution_Begin(ref int pfCancelUpdate) => VSConstants.E_NOTIMPL; + int IVsUpdateSolutionEvents.UpdateSolution_StartUpdate(ref int pfCancelUpdate) => VSConstants.E_NOTIMPL; + int IVsUpdateSolutionEvents.UpdateSolution_Cancel() => VSConstants.E_NOTIMPL; + int IVsUpdateSolutionEvents.OnActiveProjectCfgChange(IVsHierarchy pIVsHierarchy) => VSConstants.E_NOTIMPL; + + int IVsUpdateSolutionEvents.UpdateSolution_Done(int fSucceeded, int fModified, int fCancelCommand) + { + OnUpdateSolutionDone?.Invoke(); + return 0; + } + + void IDisposable.Dispose() + { + ThreadHelper.ThrowIfNotOnUIThread(); + + OnUpdateSolutionDone = null; + + if (_cookie != 0) + { + var tempCookie = _cookie; + _cookie = 0; + ErrorHandler.ThrowOnFailure(_solutionBuildManager.UnadviseUpdateSolutionEvents(tempCookie)); + } + } + } } diff --git a/src/VisualStudio/IntegrationTest/New.IntegrationTests/VisualBasic/BasicErrorListCommon.cs b/src/VisualStudio/IntegrationTest/New.IntegrationTests/VisualBasic/BasicErrorListCommon.cs index 4c17f406a807a..06976ba39a623 100644 --- a/src/VisualStudio/IntegrationTest/New.IntegrationTests/VisualBasic/BasicErrorListCommon.cs +++ b/src/VisualStudio/IntegrationTest/New.IntegrationTests/VisualBasic/BasicErrorListCommon.cs @@ -51,7 +51,7 @@ End Module await TestServices.ErrorList.NavigateToErrorListItemAsync(0, isPreview: false, shouldActivate: true, HangMitigatingCancellationToken); await TestServices.EditorVerifier.CaretPositionAsync(43, HangMitigatingCancellationToken); - await TestServices.SolutionExplorer.BuildSolutionAsync(waitForBuildToFinish: true, HangMitigatingCancellationToken); + await TestServices.SolutionExplorer.BuildSolutionAndWaitAsync(HangMitigatingCancellationToken); await TestServices.ErrorList.ShowErrorListAsync(HangMitigatingCancellationToken); await TestServices.Workspace.WaitForAllAsyncOperationsAsync(new[] { FeatureAttribute.Workspace, FeatureAttribute.SolutionCrawler, FeatureAttribute.DiagnosticService, FeatureAttribute.ErrorSquiggles, FeatureAttribute.ErrorList }, HangMitigatingCancellationToken); actualContents = await TestServices.ErrorList.GetErrorsAsync(HangMitigatingCancellationToken);