From 32a11ad1877a20c3a2c2819679a82f5ec0facf4f Mon Sep 17 00:00:00 2001 From: Georgii Borovinskikh <117642191+georgii-borovinskikh-sonarsource@users.noreply.github.com> Date: Fri, 30 Aug 2024 10:17:41 +0200 Subject: [PATCH] SLVS-1406 Add binding changed trigger to ActiveSolutionBoundTracker (#5642) [SLVS-1406](https://sonarsource.atlassian.net/browse/SLVS-1406) [SLVS-1406]: https://sonarsource.atlassian.net/browse/SLVS-1406?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ --- .../Binding/IActiveSolutionBoundTracker.cs | 5 + .../ActiveSolutionBoundTrackerTests.cs | 100 ++++++++++++++++-- .../MefServices/ActiveSolutionBoundTracker.cs | 25 +++-- 3 files changed, 115 insertions(+), 15 deletions(-) diff --git a/src/Core/Binding/IActiveSolutionBoundTracker.cs b/src/Core/Binding/IActiveSolutionBoundTracker.cs index 650c3a2e7d..77821efe2b 100644 --- a/src/Core/Binding/IActiveSolutionBoundTracker.cs +++ b/src/Core/Binding/IActiveSolutionBoundTracker.cs @@ -22,6 +22,11 @@ namespace SonarLint.VisualStudio.Core.Binding { + public interface IActiveSolutionChangedHandler + { + void HandleBindingChange(bool isBindingCleared); + } + /// /// Allows checking if the current Visual Studio solution is bound to a SonarQube project or not /// diff --git a/src/Integration.UnitTests/MefServices/ActiveSolutionBoundTrackerTests.cs b/src/Integration.UnitTests/MefServices/ActiveSolutionBoundTrackerTests.cs index bc22442154..72f88d38f8 100644 --- a/src/Integration.UnitTests/MefServices/ActiveSolutionBoundTrackerTests.cs +++ b/src/Integration.UnitTests/MefServices/ActiveSolutionBoundTrackerTests.cs @@ -218,7 +218,7 @@ public void ActiveSolutionBoundTracker_Changes() ConfigureSolutionBinding(null); // Act - host.VisualStateManager.ClearBoundProject(); + testSubject.HandleBindingChange(true); // Assert testSubject.CurrentConfiguration.Mode.Should().Be(SonarLintMode.Standalone, "Unbound solution should report false activation"); @@ -235,7 +235,7 @@ public void ActiveSolutionBoundTracker_Changes() // Case 2: Set bound project ConfigureSolutionBinding(boundProject); // Act - host.VisualStateManager.SetBoundProject(new Uri("http://localhost"), null, "project123"); + testSubject.HandleBindingChange(false); // Assert testSubject.CurrentConfiguration.Mode.Should().Be(SonarLintMode.Connected, "Bound solution should report true activation"); @@ -307,7 +307,7 @@ public void ActiveSolutionBoundTracker_Changes() // Act testSubject.Dispose(); ConfigureSolutionBinding(boundProject); - host.VisualStateManager.ClearBoundProject(); + testSubject.HandleBindingChange(true); // Assert eventCounter.PreSolutionBindingChangedCount.Should().Be(5, "Once disposed should stop raising the event"); @@ -349,13 +349,53 @@ public void OnBindingStateChanged_NewConfiguration_EventsRaisedInCorrectOrder() // Assert // Different config so event should be raised eventCounter.PreSolutionBindingChangedCount.Should().Be(1); + eventCounter.PreSolutionBindingUpdatedCount.Should().Be(0); + eventCounter.SolutionBindingChangedCount.Should().Be(1); eventCounter.SolutionBindingUpdatedCount.Should().Be(0); + + eventCounter.RaisedEventNames.Should().HaveCount(2); + eventCounter.RaisedEventNames[0].Should().Be(nameof(testSubject.PreSolutionBindingChanged)); + eventCounter.RaisedEventNames[1].Should().Be(nameof(testSubject.SolutionBindingChanged)); + } + } + + [TestMethod] + public void HandleBindingChange_NewConfiguration_EventsRaisedInCorrectOrder() + { + // Arrange + var initialProject = new BoundServerProject( + "solution", + "projectKey", + new ServerConnection.SonarCloud("myOrgKey")); + + // Set the current configuration used by the tracker + ConfigureSolutionBinding(initialProject); + using (var testSubject = CreateTestSubject(this.host, this.activeSolutionTracker, this.configProvider, loggerMock.Object)) + { + var eventCounter = new EventCounter(testSubject); + + // Now configure the provider to return a different configuration + var newProject = new BoundServerProject( + "solution", + "projectKey", + new ServerConnection.SonarCloud("myOrgKey_DIFFERENT")); + ConfigureSolutionBinding(newProject); + + // Act - simulate the binding state changing in the Team explorer section. + // The project configuration hasn't changed (it doesn't matter what properties + // we pass here; they aren't used when raising the event.) + testSubject.HandleBindingChange(false); + + // Assert + // Different config so event should be raised eventCounter.PreSolutionBindingChangedCount.Should().Be(1); + eventCounter.PreSolutionBindingUpdatedCount.Should().Be(0); + eventCounter.SolutionBindingChangedCount.Should().Be(1); eventCounter.SolutionBindingUpdatedCount.Should().Be(0); eventCounter.RaisedEventNames.Should().HaveCount(2); - eventCounter.RaisedEventNames[0].Should().Be("PreSolutionBindingChanged"); - eventCounter.RaisedEventNames[1].Should().Be("SolutionBindingChanged"); + eventCounter.RaisedEventNames[0].Should().Be(nameof(testSubject.PreSolutionBindingChanged)); + eventCounter.RaisedEventNames[1].Should().Be(nameof(testSubject.SolutionBindingChanged)); } } @@ -521,6 +561,25 @@ public void SolutionBindingUpdated_WhenClearBoundProject_NotRaised() eventCounter.SolutionBindingChangedCount.Should().Be(0); } } + + [TestMethod] + public void HandleBindingChange_WhenClearBoundProject_NotRaised() + { + // Arrange + using (var testSubject = CreateTestSubject(this.host, this.activeSolutionTracker, this.configProvider, loggerMock.Object)) + { + var eventCounter = new EventCounter(testSubject); + + // Act + testSubject.HandleBindingChange(true); + + // Assert + eventCounter.PreSolutionBindingUpdatedCount.Should().Be(0); + eventCounter.SolutionBindingUpdatedCount.Should().Be(0); + eventCounter.PreSolutionBindingChangedCount.Should().Be(0); + eventCounter.SolutionBindingChangedCount.Should().Be(0); + } + } [TestMethod] public void SolutionBindingUpdated_WhenSetBoundProject_EventsRaisedInExpectedOrder() @@ -540,8 +599,31 @@ public void SolutionBindingUpdated_WhenSetBoundProject_EventsRaisedInExpectedOrd eventCounter.SolutionBindingChangedCount.Should().Be(0); eventCounter.RaisedEventNames.Should().HaveCount(2); - eventCounter.RaisedEventNames[0].Should().Be("PreSolutionBindingUpdated"); - eventCounter.RaisedEventNames[1].Should().Be("SolutionBindingUpdated"); + eventCounter.RaisedEventNames[0].Should().Be(nameof(testSubject.PreSolutionBindingUpdated)); + eventCounter.RaisedEventNames[1].Should().Be(nameof(testSubject.SolutionBindingUpdated)); + } + } + + [TestMethod] + public void HandleBindingChange_WhenSetBoundProject_EventsRaisedInExpectedOrder() + { + // Arrange + using (var testSubject = CreateTestSubject(this.host, this.activeSolutionTracker, this.configProvider, loggerMock.Object)) + { + var eventCounter = new EventCounter(testSubject); + + // Act + testSubject.HandleBindingChange(false); + + // Assert + eventCounter.PreSolutionBindingUpdatedCount.Should().Be(1); + eventCounter.SolutionBindingUpdatedCount.Should().Be(1); + eventCounter.PreSolutionBindingChangedCount.Should().Be(0); + eventCounter.SolutionBindingChangedCount.Should().Be(0); + + eventCounter.RaisedEventNames.Should().HaveCount(2); + eventCounter.RaisedEventNames[0].Should().Be(nameof(testSubject.PreSolutionBindingUpdated)); + eventCounter.RaisedEventNames[1].Should().Be(nameof(testSubject.SolutionBindingUpdated)); } } @@ -566,8 +648,8 @@ public void GitRepoUpdated_SolutionBindingUpdatedEventsRaised() eventCounter.SolutionBindingChangedCount.Should().Be(0); eventCounter.RaisedEventNames.Should().HaveCount(2); - eventCounter.RaisedEventNames[0].Should().Be("PreSolutionBindingUpdated"); - eventCounter.RaisedEventNames[1].Should().Be("SolutionBindingUpdated"); + eventCounter.RaisedEventNames[0].Should().Be(nameof(testSubject.PreSolutionBindingUpdated)); + eventCounter.RaisedEventNames[1].Should().Be(nameof(testSubject.SolutionBindingUpdated)); } } diff --git a/src/Integration/MefServices/ActiveSolutionBoundTracker.cs b/src/Integration/MefServices/ActiveSolutionBoundTracker.cs index a644023f0b..4c0f4fa5f1 100644 --- a/src/Integration/MefServices/ActiveSolutionBoundTracker.cs +++ b/src/Integration/MefServices/ActiveSolutionBoundTracker.cs @@ -41,8 +41,9 @@ namespace SonarLint.VisualStudio.Integration /// UIContext. /// [Export(typeof(IActiveSolutionBoundTracker))] + [Export(typeof(IActiveSolutionChangedHandler))] [PartCreationPolicy(CreationPolicy.Shared)] - internal sealed class ActiveSolutionBoundTracker : IActiveSolutionBoundTracker, IDisposable, IPartImportsSatisfiedNotification + internal sealed class ActiveSolutionBoundTracker : IActiveSolutionBoundTracker, IActiveSolutionChangedHandler, IDisposable, IPartImportsSatisfiedNotification { private readonly IHost extensionHost; private readonly IActiveSolutionTracker solutionTracker; @@ -52,6 +53,7 @@ internal sealed class ActiveSolutionBoundTracker : IActiveSolutionBoundTracker, private readonly IConfigScopeUpdater configScopeUpdater; private readonly ILogger logger; private readonly uint boundSolutionContextCookie; + private bool disposed; public event EventHandler PreSolutionBindingChanged; public event EventHandler SolutionBindingChanged; @@ -95,6 +97,21 @@ public ActiveSolutionBoundTracker([Import(typeof(SVsServiceProvider))] IServiceP this.gitEventsMonitor.HeadChanged += GitEventsMonitor_HeadChanged; } + public void HandleBindingChange(bool isBindingCleared) + { + if (disposed) + { + return; + } + + this.RaiseAnalyzersChangedIfBindingChanged(GetBindingConfiguration(), isBindingCleared); + } + + private void OnBindingStateChanged(object sender, BindingStateEventArgs e) + { + HandleBindingChange(e.IsBindingCleared); + } + private BindingConfiguration GetBindingConfiguration() { return configurationProvider.GetConfiguration(); @@ -159,11 +176,6 @@ await Core.WebServiceHelper.SafeServiceCallAsync(() => sonarQubeService.ConnectA } } - private void OnBindingStateChanged(object sender, BindingStateEventArgs e) - { - this.RaiseAnalyzersChangedIfBindingChanged(GetBindingConfiguration(), e.IsBindingCleared); - } - private void RaiseAnalyzersChangedIfBindingChanged(BindingConfiguration newBindingConfiguration, bool? isBindingCleared = null) { configScopeUpdater.UpdateConfigScopeForCurrentSolution(newBindingConfiguration.Project); @@ -206,6 +218,7 @@ private void Dispose(bool disposing) { if (disposing) { + this.disposed = true; this.solutionTracker.ActiveSolutionChanged -= this.OnActiveSolutionChanged; this.extensionHost.VisualStateManager.BindingStateChanged -= this.OnBindingStateChanged; this.gitEventsMonitor.HeadChanged -= GitEventsMonitor_HeadChanged;