Skip to content

Commit

Permalink
Make ctor in StatusBarNotifier free threaded. (#4874)
Browse files Browse the repository at this point in the history
* Make ctor in StatusBarNotifier free threaded.

* remove threadhandling
  • Loading branch information
bigfluffycookie authored Sep 13, 2023
1 parent d22e321 commit 5772fa5
Show file tree
Hide file tree
Showing 2 changed files with 59 additions and 29 deletions.
62 changes: 47 additions & 15 deletions src/Integration.Vsix.UnitTests/Helpers/StatusBarNotifierTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,13 @@
*/

using System;
using System.Collections.Generic;
using FluentAssertions;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using SonarLint.VisualStudio.Core;
using SonarLint.VisualStudio.Infrastructure.VS;
using SonarLint.VisualStudio.Integration.Vsix.Helpers;
using SonarLint.VisualStudio.TestInfrastructure;

Expand All @@ -30,22 +34,27 @@ namespace SonarLint.VisualStudio.Integration.UnitTests.Helpers
[TestClass]
public class StatusBarNotifierTests
{
private Mock<IVsStatusbar> statusBarMock;
private StatusBarNotifier testSubject;
private object statusIcon;
private object statusIcon = (short) Microsoft.VisualStudio.Shell.Interop.Constants.SBAI_General;

[TestInitialize]
public void TestInitialize()
{
statusIcon = (short)Microsoft.VisualStudio.Shell.Interop.Constants.SBAI_General;
statusBarMock = new Mock<IVsStatusbar>();
[TestMethod]
public void MefCtor_CheckIsExported()
=> MefTestHelpers.CheckTypeCanBeImported<StatusBarNotifier, IStatusBarNotifier>(
MefTestHelpers.CreateExport<IVsUIServiceOperation>());

var serviceProviderMock = new Mock<IServiceProvider>();
serviceProviderMock.Setup(x => x.GetService(typeof(IVsStatusbar))).Returns(statusBarMock.Object);
[TestMethod]
public void MefCtor_CheckIsSingleton()
=> MefTestHelpers.CheckIsSingletonMefComponent<StatusBarNotifier>();

testSubject = new StatusBarNotifier(serviceProviderMock.Object);
[TestMethod]
public void MefCtor_DoesNotCallAnyServices()
{
var serviceOp = new Mock<IVsUIServiceOperation>();

_ = CreateTestSubject(serviceOp.Object);

ThreadHelper.SetCurrentThreadAsUIThread();
// The MEF constructor should be free-threaded, which it will be if
// it doesn't make any external calls.
serviceOp.Invocations.Should().BeEmpty();
}

[TestMethod]
Expand All @@ -55,11 +64,34 @@ public void Notify_DisplaysMessageAndSpinner(bool showSpinner)
{
var expectedMessage = Guid.NewGuid().ToString();

var statusBar = new Mock<IVsStatusbar>();
var serviceOp = CreateServiceOperation(statusBar.Object);
var testSubject = CreateTestSubject(serviceOp);
testSubject.Notify(expectedMessage, showSpinner);

statusBarMock.Verify(x => x.SetText(expectedMessage), Times.Once);
statusBarMock.Verify(x => x.Animation(showSpinner ? 1 : 0, ref statusIcon), Times.Once);
statusBarMock.VerifyNoOtherCalls();
statusBar.Verify(x => x.SetText(expectedMessage), Times.Once);
statusBar.Verify(x => x.Animation(showSpinner ? 1 : 0, ref statusIcon), Times.Once);
statusBar.VerifyNoOtherCalls();
}

private IVsUIServiceOperation CreateServiceOperation(IVsStatusbar svcToPassToCallback, Action callback = null)
{
var serviceOp = new Mock<IVsUIServiceOperation>();

// Set up the mock to invoke the operation with the supplied VS service
serviceOp.Setup(x => x.Execute<IVsStatusbar, IVsStatusbar>(It.IsAny<Action<IVsStatusbar>>()))
.Callback<Action<IVsStatusbar>>(op => {
callback?.Invoke();
op(svcToPassToCallback);
});

return serviceOp.Object;
}

private static StatusBarNotifier CreateTestSubject(IVsUIServiceOperation vsUIServiceOperation)
{
var testSubject = new StatusBarNotifier(vsUIServiceOperation);
return testSubject;
}
}
}
26 changes: 12 additions & 14 deletions src/Integration.Vsix/Helpers/IStatusBarNotifier.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,9 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

using System;
using System.ComponentModel.Composition;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using SonarLint.VisualStudio.Core;
using SonarLint.VisualStudio.Infrastructure.VS;

namespace SonarLint.VisualStudio.Integration.Vsix.Helpers
Expand All @@ -35,26 +34,25 @@ internal interface IStatusBarNotifier
[PartCreationPolicy(CreationPolicy.Shared)]
internal class StatusBarNotifier : IStatusBarNotifier
{
private IVsStatusbar vsStatusBar;
private readonly IVsUIServiceOperation vSServiceOperation;

[ImportingConstructor]
public StatusBarNotifier([Import(typeof(SVsServiceProvider))] IServiceProvider serviceProvider)
public StatusBarNotifier(IVsUIServiceOperation vSServiceOperation)
{
RunOnUIThread.Run(() =>
{
vsStatusBar = serviceProvider.GetService(typeof(IVsStatusbar)) as IVsStatusbar;
});
this.vSServiceOperation = vSServiceOperation;
}

public void Notify(string message, bool showSpinner)
{
RunOnUIThread.Run(() =>
{
object icon = (short)Microsoft.VisualStudio.Shell.Interop.Constants.SBAI_General;
vsStatusBar.Animation(showSpinner ? 1 : 0, ref icon);
vSServiceOperation.Execute<IVsStatusbar, IVsStatusbar>(vsStatusBar => DoNotify(vsStatusBar, message, showSpinner));
}

private void DoNotify(IVsStatusbar vsStatusBar, string message, bool showSpinner)
{
object icon = (short)Microsoft.VisualStudio.Shell.Interop.Constants.SBAI_General;
vsStatusBar.Animation(showSpinner ? 1 : 0, ref icon);

vsStatusBar.SetText(message);
});
vsStatusBar.SetText(message);
}
}
}

0 comments on commit 5772fa5

Please sign in to comment.