Skip to content

Commit

Permalink
SLVS-1537 Add ISolutionRoslynAnalyzerManager integration to ActiveSol…
Browse files Browse the repository at this point in the history
…utionBoundTracker (#5768)
  • Loading branch information
georgii-borovinskikh-sonarsource committed Nov 5, 2024
1 parent 5be2d39 commit b534a03
Show file tree
Hide file tree
Showing 2 changed files with 102 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
using Moq;
using SonarLint.VisualStudio.Core;
using SonarLint.VisualStudio.Core.Binding;
using SonarLint.VisualStudio.Infrastructure.VS.Roslyn;
using SonarLint.VisualStudio.TestInfrastructure;
using SonarQube.Client;
using SonarQube.Client.Models;
Expand Down Expand Up @@ -158,11 +159,73 @@ public void ActiveSolutionBoundTracker_UnBoundProject_NullPassedToConfigScopeUpd
configScopeUpdaterMock.Verify(x => x.UpdateConfigScopeForCurrentSolution(null));
configScopeUpdaterMock.VerifyNoOtherCalls();
}

[TestMethod]
public void Ctor_NoSolution_CallsAnalyzerManager()
{
var configScopeUpdaterMock = new Mock<IConfigScopeUpdater>();
var analyzerManager = new Mock<ISolutionRoslynAnalyzerManager>();

activeSolutionTracker.CurrentSolutionName = null;
ConfigureService(isConnected: false);

var testSubject = CreateTestSubject(
activeSolutionTracker,
configProvider,
loggerMock.Object,
configScopeUpdater: configScopeUpdaterMock.Object,
sonarQubeService: sonarQubeServiceMock.Object,
solutionRoslynAnalyzerManager: analyzerManager.Object);

analyzerManager.Verify(x => x.OnSolutionChanged(null, BindingConfiguration.Standalone));
}

[TestMethod]
public void Ctor_StandaloneSolution_CallsAnalyzerManager()
{
var configScopeUpdaterMock = new Mock<IConfigScopeUpdater>();
var analyzerManager = new Mock<ISolutionRoslynAnalyzerManager>();

activeSolutionTracker.CurrentSolutionName = "solution123";
ConfigureService(isConnected: false);

var testSubject = CreateTestSubject(
activeSolutionTracker,
configProvider,
loggerMock.Object,
configScopeUpdater: configScopeUpdaterMock.Object,
sonarQubeService: sonarQubeServiceMock.Object,
solutionRoslynAnalyzerManager: analyzerManager.Object);

analyzerManager.Verify(x => x.OnSolutionChanged("solution123", BindingConfiguration.Standalone));
}

[TestMethod]
public void Ctor_BoundSolution_CallsAnalyzerManager()
{
var configScopeUpdaterMock = new Mock<IConfigScopeUpdater>();
var analyzerManager = new Mock<ISolutionRoslynAnalyzerManager>();

activeSolutionTracker.CurrentSolutionName = "solution123";
ConfigureService(isConnected: false);
ConfigureSolutionBinding(boundSonarQubeProject);

var testSubject = CreateTestSubject(
activeSolutionTracker,
configProvider,
loggerMock.Object,
configScopeUpdater: configScopeUpdaterMock.Object,
sonarQubeService: sonarQubeServiceMock.Object,
solutionRoslynAnalyzerManager: analyzerManager.Object);

analyzerManager.Verify(x => x.OnSolutionChanged("solution123", It.Is<BindingConfiguration>(y => y.Mode == SonarLintMode.Connected && y.Project == boundSonarQubeProject)));
}

[TestMethod]
public void ActiveSolutionBoundTracker_Changes()
{
var configScopeUpdaterMock = new Mock<IConfigScopeUpdater>();
var analyzerManager = new Mock<ISolutionRoslynAnalyzerManager>();

ConfigureService(isConnected: false);
ConfigureSolutionBinding(boundSonarQubeProject);
Expand All @@ -172,7 +235,8 @@ public void ActiveSolutionBoundTracker_Changes()
configProvider,
loggerMock.Object,
configScopeUpdater: configScopeUpdaterMock.Object,
sonarQubeService: sonarQubeServiceMock.Object);
sonarQubeService: sonarQubeServiceMock.Object,
solutionRoslynAnalyzerManager: analyzerManager.Object);
var eventCounter = new EventCounter(testSubject);

// Sanity
Expand All @@ -185,6 +249,7 @@ public void ActiveSolutionBoundTracker_Changes()

// Case 1: Clear bound project
ConfigureSolutionBinding(null);
activeSolutionTracker.CurrentSolutionName = "solution1";

// Act
testSubject.HandleBindingChange(true);
Expand All @@ -195,7 +260,8 @@ public void ActiveSolutionBoundTracker_Changes()
eventCounter.SolutionBindingChangedCount.Should().Be(1, "Unbind should trigger reanalysis");
eventCounter.PreSolutionBindingUpdatedCount.Should().Be(0, "Unbind should not trigger change");
eventCounter.SolutionBindingUpdatedCount.Should().Be(0, "Unbind should not trigger change");
configScopeUpdaterMock.Verify(x => x.UpdateConfigScopeForCurrentSolution(It.IsAny<BoundServerProject>()), Times.Exactly(1));
configScopeUpdaterMock.Verify(x => x.UpdateConfigScopeForCurrentSolution(null), Times.Exactly(1));
VerifyAnalyzerUpdatedAndClearMock(analyzerManager, "solution1", false);
VerifyAndResetBoundSolutionUiContextMock(isActive: false);

VerifyServiceDisconnect(Times.Never());
Expand All @@ -213,6 +279,7 @@ public void ActiveSolutionBoundTracker_Changes()
eventCounter.PreSolutionBindingUpdatedCount.Should().Be(0, "Bind should not trigger update event");
eventCounter.SolutionBindingUpdatedCount.Should().Be(0, "Bind should not trigger update event");
configScopeUpdaterMock.Verify(x => x.UpdateConfigScopeForCurrentSolution(It.IsAny<BoundServerProject>()), Times.Exactly(2));
VerifyAnalyzerUpdatedAndClearMock(analyzerManager, "solution1", true);
VerifyAndResetBoundSolutionUiContextMock(isActive: true);

// Notifications from the Team Explorer should not trigger connect/disconnect
Expand All @@ -231,6 +298,7 @@ public void ActiveSolutionBoundTracker_Changes()
eventCounter.PreSolutionBindingUpdatedCount.Should().Be(0, "Solution change should not trigger update event");
eventCounter.SolutionBindingUpdatedCount.Should().Be(0, "Solution change should not trigger update event");
configScopeUpdaterMock.Verify(x => x.UpdateConfigScopeForCurrentSolution(It.IsAny<BoundServerProject>()), Times.Exactly(3));
VerifyAnalyzerUpdatedAndClearMock(analyzerManager, null, false);
VerifyAndResetBoundSolutionUiContextMock(isActive: false);

// Closing an unbound solution should not call disconnect/connect
Expand All @@ -240,7 +308,7 @@ public void ActiveSolutionBoundTracker_Changes()
// Case 4: Load a bound solution
ConfigureSolutionBinding(boundSonarQubeProject);
// Act
activeSolutionTracker.SimulateActiveSolutionChanged(isSolutionOpen: true, "solution");
activeSolutionTracker.SimulateActiveSolutionChanged(isSolutionOpen: true, "solution2");

// Assert
testSubject.CurrentConfiguration.Mode.Should().Be(SonarLintMode.Connected, "Bound respond to solution change event and report bound");
Expand All @@ -249,6 +317,7 @@ public void ActiveSolutionBoundTracker_Changes()
eventCounter.PreSolutionBindingUpdatedCount.Should().Be(0, "Bind should not trigger update event");
eventCounter.SolutionBindingUpdatedCount.Should().Be(0, "Bind should not trigger update event");
configScopeUpdaterMock.Verify(x => x.UpdateConfigScopeForCurrentSolution(It.IsAny<BoundServerProject>()), Times.Exactly(4));
VerifyAnalyzerUpdatedAndClearMock(analyzerManager, "solution2", true);
VerifyAndResetBoundSolutionUiContextMock(isActive: true);

// Loading a bound solution should call connect
Expand All @@ -266,6 +335,7 @@ public void ActiveSolutionBoundTracker_Changes()
eventCounter.PreSolutionBindingUpdatedCount.Should().Be(0, "Solution change should not trigger update event");
eventCounter.SolutionBindingUpdatedCount.Should().Be(0, "Solution change should not trigger update event");
configScopeUpdaterMock.Verify(x => x.UpdateConfigScopeForCurrentSolution(It.IsAny<BoundServerProject>()), Times.Exactly(5));
VerifyAnalyzerUpdatedAndClearMock(analyzerManager, null, false);
VerifyAndResetBoundSolutionUiContextMock(isActive: false);

// SonarQubeService.Disconnect should be called since the WPF DisconnectCommand is not available
Expand All @@ -282,11 +352,27 @@ public void ActiveSolutionBoundTracker_Changes()
eventCounter.PreSolutionBindingChangedCount.Should().Be(5, "Once disposed should stop raising the event");
eventCounter.SolutionBindingChangedCount.Should().Be(5, "Once disposed should stop raising the event");
configScopeUpdaterMock.Verify(x => x.UpdateConfigScopeForCurrentSolution(It.IsAny<BoundServerProject>()), Times.Exactly(5));
analyzerManager.Invocations.Should().BeEmpty();
// SonarQubeService.Disconnect should be called since the WPF DisconnectCommand is not available
VerifyServiceDisconnect(Times.Once());
VerifyServiceConnect(Times.Once());
}

private void VerifyAnalyzerUpdatedAndClearMock(Mock<ISolutionRoslynAnalyzerManager> analyzerManager, string solutionName, bool bound)
{
if (bound)
{
analyzerManager.Verify(x => x.OnSolutionChanged(solutionName,
It.Is<BindingConfiguration>(y => y.Mode == SonarLintMode.Connected && y.Project == boundSonarQubeProject)),
Times.Exactly(1));
}
else
{
analyzerManager.Verify(x => x.OnSolutionChanged(solutionName, BindingConfiguration.Standalone), Times.Exactly(1));
}
analyzerManager.Invocations.Clear();
}

[TestMethod]
public void UpdateConnection_WasDisconnected_NewSolutionIsUnbound_NoConnectOrDisconnectCalls()
{
Expand Down Expand Up @@ -487,13 +573,15 @@ private ActiveSolutionBoundTracker CreateTestSubject(
ILogger logger = null,
IBoundSolutionGitMonitor gitEvents = null,
IConfigScopeUpdater configScopeUpdater = null,
ISonarQubeService sonarQubeService = null)
ISonarQubeService sonarQubeService = null,
ISolutionRoslynAnalyzerManager solutionRoslynAnalyzerManager = null)
{
configScopeUpdater ??= Mock.Of<IConfigScopeUpdater>();
logger ??= new TestLogger(logToConsole: true);
gitEvents ??= Mock.Of<IBoundSolutionGitMonitor>();
sonarQubeService ??= Mock.Of<ISonarQubeService>();
return new ActiveSolutionBoundTracker(serviceProvider, solutionTracker, configScopeUpdater, logger, gitEvents, configurationProvider, sonarQubeService);
solutionRoslynAnalyzerManager ??= Mock.Of<ISolutionRoslynAnalyzerManager>();
return new ActiveSolutionBoundTracker(serviceProvider, solutionTracker, configScopeUpdater, solutionRoslynAnalyzerManager, logger, gitEvents, configurationProvider, sonarQubeService);
}

private void ConfigureService(bool isConnected)
Expand Down
12 changes: 9 additions & 3 deletions src/Integration/MefServices/ActiveSolutionBoundTracker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
using Microsoft.VisualStudio.Shell.Interop;
using SonarLint.VisualStudio.Core;
using SonarLint.VisualStudio.Core.Binding;
using SonarLint.VisualStudio.Infrastructure.VS.Roslyn;
using SonarQube.Client;
using ErrorHandler = Microsoft.VisualStudio.ErrorHandler;
using Task = System.Threading.Tasks.Task;
Expand All @@ -48,6 +49,7 @@ internal sealed class ActiveSolutionBoundTracker : IActiveSolutionBoundTracker,
private readonly IVsMonitorSelection vsMonitorSelection;
private readonly IBoundSolutionGitMonitor gitEventsMonitor;
private readonly IConfigScopeUpdater configScopeUpdater;
private readonly ISolutionRoslynAnalyzerManager solutionRoslynAnalyzerManager;
private readonly ILogger logger;
private readonly uint boundSolutionContextCookie;
private bool disposed;
Expand All @@ -62,6 +64,7 @@ internal sealed class ActiveSolutionBoundTracker : IActiveSolutionBoundTracker,
public ActiveSolutionBoundTracker([Import(typeof(SVsServiceProvider))] IServiceProvider serviceProvider,
IActiveSolutionTracker activeSolutionTracker,
IConfigScopeUpdater configScopeUpdater,
ISolutionRoslynAnalyzerManager solutionRoslynAnalyzerManager,
ILogger logger,
IBoundSolutionGitMonitor gitEventsMonitor,
IConfigurationProvider configurationProvider,
Expand All @@ -79,11 +82,13 @@ public ActiveSolutionBoundTracker([Import(typeof(SVsServiceProvider))] IServiceP
this.configurationProvider = configurationProvider;
this.sonarQubeService = sonarQubeService;
this.configScopeUpdater = configScopeUpdater;
this.solutionRoslynAnalyzerManager = solutionRoslynAnalyzerManager;

// The solution changed inside the IDE
solutionTracker.ActiveSolutionChanged += OnActiveSolutionChanged;

CurrentConfiguration = GetBindingConfiguration();
solutionRoslynAnalyzerManager.OnSolutionChanged(activeSolutionTracker.CurrentSolutionName, CurrentConfiguration);

SetBoundSolutionUIContext();

Expand All @@ -97,7 +102,7 @@ public void HandleBindingChange(bool isBindingCleared)
return;
}

this.RaiseAnalyzersChangedIfBindingChanged(GetBindingConfiguration(), isBindingCleared);
this.RaiseAnalyzersChangedIfBindingChanged(GetBindingConfiguration(), solutionTracker.CurrentSolutionName, isBindingCleared);
}

private BindingConfiguration GetBindingConfiguration()
Expand Down Expand Up @@ -127,7 +132,7 @@ private async void OnActiveSolutionChanged(object sender, ActiveSolutionChangedE

gitEventsMonitor.Refresh();

this.RaiseAnalyzersChangedIfBindingChanged(newBindingConfiguration);
this.RaiseAnalyzersChangedIfBindingChanged(newBindingConfiguration, args.SolutionName);
}
catch (Exception ex) when (!ErrorHandler.IsCriticalException(ex))
{
Expand Down Expand Up @@ -155,9 +160,10 @@ await WebServiceHelper.SafeServiceCallAsync(() => sonarQubeService.ConnectAsync(
}
}

private void RaiseAnalyzersChangedIfBindingChanged(BindingConfiguration newBindingConfiguration, bool? isBindingCleared = null)
private void RaiseAnalyzersChangedIfBindingChanged(BindingConfiguration newBindingConfiguration, string solutionName, bool? isBindingCleared = null)
{
configScopeUpdater.UpdateConfigScopeForCurrentSolution(newBindingConfiguration.Project);
solutionRoslynAnalyzerManager.OnSolutionChanged(solutionName, newBindingConfiguration);

if (!CurrentConfiguration.Equals(newBindingConfiguration))
{
Expand Down

0 comments on commit b534a03

Please sign in to comment.