Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,25 @@
*/

using System.ComponentModel.Composition.Primitives;
using SonarLint.VisualStudio.Core;
using SonarLint.VisualStudio.Core.Initialization;
using SonarLint.VisualStudio.Integration;
using SonarLint.VisualStudio.IssueVisualization.NewCode;
using SonarLint.VisualStudio.SLCore.Core;
using SonarLint.VisualStudio.SLCore.Service.NewCode;
using SonarLint.VisualStudio.TestInfrastructure;

namespace SonarLint.VisualStudio.Core.UnitTests;
namespace SonarLint.VisualStudio.IssueVisualization.UnitTests.NewCode;

[TestClass]
public class FocusOnNewCodeServiceTests
{
private ISonarLintSettings sonarLintSettings;
private NoOpThreadHandler threadHandling;
private IInitializationProcessorFactory initializationProcessorFactory;
private NoOpThreadHandler threadHandler;
private ISLCoreServiceProvider serviceProvider;
private INewCodeSLCoreService newCodeSlCoreService;
private FocusOnNewCodeService testSubject;

[TestInitialize]
Expand All @@ -42,7 +49,11 @@ private void TestInitialize(bool isEnabled)
sonarLintSettings.IsFocusOnNewCodeEnabled.Returns(isEnabled);
threadHandling = Substitute.ForPartsOf<NoOpThreadHandler>();
initializationProcessorFactory = MockableInitializationProcessor.CreateFactory<FocusOnNewCodeService>(threadHandling, Substitute.ForPartsOf<TestLogger>());
testSubject = new FocusOnNewCodeService(sonarLintSettings, initializationProcessorFactory);
threadHandler = Substitute.ForPartsOf<NoOpThreadHandler>();
serviceProvider = Substitute.For<ISLCoreServiceProvider>();
newCodeSlCoreService = Substitute.For<INewCodeSLCoreService>();
SetUpSlCoreService(true);
testSubject = new FocusOnNewCodeService(sonarLintSettings, initializationProcessorFactory, serviceProvider, threadHandler);
testSubject.InitializationProcessor.InitializeAsync().GetAwaiter().GetResult();
}

Expand All @@ -52,7 +63,9 @@ public void MefCtor_CheckIsExported()
Export[] exports =
[
MefTestHelpers.CreateExport<ISonarLintSettings>(),
MefTestHelpers.CreateExport<IInitializationProcessorFactory>()
MefTestHelpers.CreateExport<IInitializationProcessorFactory>(),
MefTestHelpers.CreateExport<ISLCoreServiceProvider>(),
MefTestHelpers.CreateExport<IThreadHandling>(),
];

MefTestHelpers.CheckTypeCanBeImported<FocusOnNewCodeService, IFocusOnNewCodeService>(exports);
Expand All @@ -79,6 +92,7 @@ public void Ctor_InitializesCorrectly(bool isEnabled)
_ = sonarLintSettings.IsFocusOnNewCodeEnabled; // this doesn't actually assert anything due to how NSub works, but is left here to make the test easier to understand
testSubject.InitializationProcessor.InitializeAsync(); // from CreateTestSubject
});
serviceProvider.DidNotReceiveWithAnyArgs().TryGetTransientService(out Arg.Any<INewCodeSLCoreService>());
}

[DataTestMethod]
Expand All @@ -95,4 +109,31 @@ public void Set_UpdatesSettingAndRaisesEvent(bool isEnabled)
testSubject.Current.IsEnabled.Should().Be(isEnabled);
handler.Received(1).Invoke(testSubject, Arg.Is<NewCodeStatusChangedEventArgs>(e => e.NewStatus.IsEnabled == isEnabled));
}

[DataTestMethod]
[DataRow(true)]
[DataRow(false)]
public void Set_NotifiesSlCore(bool isSlCoreInitialized)
{
SetUpSlCoreService(isSlCoreInitialized);

testSubject.Set(true);

Received.InOrder(() =>
{
threadHandling.RunOnBackgroundThread(Arg.Any<Func<Task<int>>>());
serviceProvider.TryGetTransientService(out Arg.Any<INewCodeSLCoreService>());
if (isSlCoreInitialized)
{
newCodeSlCoreService.DidToggleFocus();
}
});
}

private void SetUpSlCoreService(bool isInitialized) =>
serviceProvider.TryGetTransientService(out Arg.Any<INewCodeSLCoreService>()).Returns(info =>
{
info[0] = newCodeSlCoreService;
return isInitialized;
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,22 +19,34 @@
*/

using System.ComponentModel.Composition;
using Microsoft.VisualStudio.Threading;
using SonarLint.VisualStudio.Core;
using SonarLint.VisualStudio.Core.Initialization;
using SonarLint.VisualStudio.Integration;
using SonarLint.VisualStudio.SLCore;
using SonarLint.VisualStudio.SLCore.Core;
using SonarLint.VisualStudio.SLCore.Service.NewCode;

namespace SonarLint.VisualStudio.Core;
namespace SonarLint.VisualStudio.IssueVisualization.NewCode;

[Export(typeof(IFocusOnNewCodeService))]
[Export(typeof(IFocusOnNewCodeServiceUpdater))]
[PartCreationPolicy(CreationPolicy.Shared)]
public class FocusOnNewCodeService : IFocusOnNewCodeServiceUpdater
{
private readonly ISonarLintSettings sonarLintSettings;
private readonly ISLCoreServiceProvider slCoreServiceProvider;
private readonly IThreadHandling threadHandling;

[ImportingConstructor]
public FocusOnNewCodeService(ISonarLintSettings sonarLintSettings, IInitializationProcessorFactory initializationProcessorFactory)
public FocusOnNewCodeService(ISonarLintSettings sonarLintSettings,
IInitializationProcessorFactory initializationProcessorFactory,
ISLCoreServiceProvider slCoreServiceProvider,
IThreadHandling threadHandling)
{
this.sonarLintSettings = sonarLintSettings;
this.slCoreServiceProvider = slCoreServiceProvider;
this.threadHandling = threadHandling;
InitializationProcessor = initializationProcessorFactory.CreateAndStart<FocusOnNewCodeService>([], () =>
{
// sonarLintSettings needs UI thread to initialize settings storage, so the first property access may not be free-threaded
Expand All @@ -49,8 +61,18 @@ public void Set(bool isEnabled)
{
sonarLintSettings.IsFocusOnNewCodeEnabled = isEnabled;
Current = new(sonarLintSettings.IsFocusOnNewCodeEnabled);
NotifySlCoreNewCodeToggled();
Changed?.Invoke(this, new(Current));
}

private void NotifySlCoreNewCodeToggled() =>
threadHandling.RunOnBackgroundThread(() =>
{
if (slCoreServiceProvider.TryGetTransientService(out INewCodeSLCoreService newCodeService))
{
newCodeService.DidToggleFocus();
}
}).Forget();

public event EventHandler<NewCodeStatusChangedEventArgs> Changed;
}
6 changes: 5 additions & 1 deletion src/SLCore.IntegrationTests/SLCoreTestRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,9 @@ public async Task Start(TestLogger testLogger)
noOpActiveSolutionBoundTracker.CurrentConfiguration.Returns(BindingConfiguration.Standalone);
var noOpConfigScopeUpdater = Substitute.For<IConfigScopeUpdater>();

var focusOnNewCodeService = Substitute.For<IFocusOnNewCodeService>();
focusOnNewCodeService.Current.Returns(new FocusOnNewCodeStatus(false));

slCoreInstanceHandle = new SLCoreInstanceHandle(new SLCoreRpcFactory(slCoreTestProcessFactory, slCoreLocator,
new SLCoreJsonRpcFactory(new RpcMethodNameTransformer()),
new RpcDebugger(new FileSystem(), Path.Combine(privateFolder, "logrpc.log")),
Expand All @@ -141,6 +144,7 @@ public async Task Start(TestLogger testLogger)
noOpConfigScopeUpdater,
slCoreRulesSettingsProvider,
Substitute.For<ISlCoreTelemetryMigrationProvider>(),
focusOnNewCodeService,
new NoOpThreadHandler());

await InitializeAndWaitForSloopLog(testLogger);
Expand All @@ -157,7 +161,7 @@ private async Task InitializeAndWaitForSloopLog(TestLogger slCoreLogger)
EventHandler eventHandler = (_, _) => tcs.TrySetResult(true);
slCoreLogger.LogMessageAdded += eventHandler;

slCoreInstanceHandle.Initialize();
await slCoreInstanceHandle.InitializeAsync();

try
{
Expand Down
3 changes: 3 additions & 0 deletions src/SLCore.UnitTests/SLCoreInstanceFactoryTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ public void MefCtor_CheckIsExported()
MefTestHelpers.CreateExport<IThreadHandling>(),
MefTestHelpers.CreateExport<ISLCoreRuleSettingsProvider>(),
MefTestHelpers.CreateExport<IEsLintBridgeLocator>(),
MefTestHelpers.CreateExport<IFocusOnNewCodeService>(),
MefTestHelpers.CreateExport<ISlCoreTelemetryMigrationProvider>());
}

Expand All @@ -76,6 +77,7 @@ public void CreateInstance_ReturnsNonNull()
var threadHandling = Substitute.For<IThreadHandling>();
var slCoreRuleSettingsProvider = Substitute.For<ISLCoreRuleSettingsProvider>();
var telemetryMigrationProvider = Substitute.For<ISlCoreTelemetryMigrationProvider>();
var focusOnNewCodeService = Substitute.For<IFocusOnNewCodeService>();

var testSubject = new SLCoreInstanceFactory(
islCoreRpcFactory,
Expand All @@ -91,6 +93,7 @@ public void CreateInstance_ReturnsNonNull()
slCoreRuleSettingsProvider,
telemetryMigrationProvider,
esLintBridgeLocator,
focusOnNewCodeService,
threadHandling);

testSubject.CreateInstance().Should().NotBeNull();
Expand Down
20 changes: 12 additions & 8 deletions src/SLCore.UnitTests/SLCoreInstanceHandleTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ public class SLCoreInstanceHandleTests
private ISLCoreRuleSettingsProvider slCoreRuleSettingsProvider;
private SLCoreInstanceHandle testSubject;
private ISlCoreTelemetryMigrationProvider telemetryMigrationProvider;
private IFocusOnNewCodeService focusOnNewCodeService;

[TestInitialize]
public void TestInitialize()
Expand All @@ -90,6 +91,8 @@ public void TestInitialize()
threadHandling = Substitute.ForPartsOf<NoOpThreadHandler>();
slCoreRuleSettingsProvider = Substitute.For<ISLCoreRuleSettingsProvider>();
telemetryMigrationProvider = Substitute.For<ISlCoreTelemetryMigrationProvider>();
focusOnNewCodeService = Substitute.For<IFocusOnNewCodeService>();
focusOnNewCodeService.Current.Returns(new FocusOnNewCodeStatus(false));

testSubject = new SLCoreInstanceHandle(
slCoreRpcFactory,
Expand All @@ -105,6 +108,7 @@ public void TestInitialize()
configScopeUpdater,
slCoreRuleSettingsProvider,
telemetryMigrationProvider,
focusOnNewCodeService,
threadHandling);
}

Expand All @@ -116,15 +120,15 @@ public void Initialize_RpcManagerThrows_DoesNotCatch()
var exception = new Exception(exceptionMessage);
rpcManager.When(x => x.Initialize(Arg.Any<InitializeParams>())).Throw(exception);

var act = () => testSubject.Initialize();
var act = () => testSubject.InitializeAsync();

act.Should().ThrowExactly<Exception>().WithMessage(exceptionMessage);
}

[DataTestMethod]
[DataRow("some/node/path", "vsix/esLintBridge")]
[DataRow(null, null)]
public void Initialize_SuccessfullyInitializesInCorrectOrder(string nodeJsPath, string esLintBridgePath)
public async Task Initialize_SuccessfullyInitializesInCorrectOrder(string nodeJsPath, string esLintBridgePath)
{
SetUpLanguages([], []);
SetUpFullConfiguration(out _);
Expand All @@ -133,7 +137,7 @@ public void Initialize_SuccessfullyInitializesInCorrectOrder(string nodeJsPath,
var telemetryMigrationDto = new TelemetryMigrationDto(default, default, default);
telemetryMigrationProvider.Get().Returns(telemetryMigrationDto);

testSubject.Initialize();
await testSubject.InitializeAsync();

Received.InOrder(() =>
{
Expand Down Expand Up @@ -170,7 +174,7 @@ public void Initialize_UsesProvidedLanguageConfiguration()
SetUpLanguages(standalone, connected);
SetUpFullConfiguration(out _);

testSubject.Initialize();
testSubject.InitializeAsync();

var initializeParams = (InitializeParams)rpcManager.ReceivedCalls().Single().GetArguments().Single()!;
initializeParams.enabledLanguagesInStandaloneMode.Should().BeSameAs(standalone);
Expand All @@ -183,7 +187,7 @@ public void Initialize_ProvidesRulesSettings()
SetUpFullConfiguration(out _);
slCoreRuleSettingsProvider.GetSLCoreRuleSettings().Returns(new Dictionary<string, StandaloneRuleConfigDto>() { { "rule1", new StandaloneRuleConfigDto(true, []) } });

testSubject.Initialize();
testSubject.InitializeAsync();

rpcManager.Received(1).Initialize(Arg.Is<InitializeParams>(param => param.standaloneRuleConfigByKey.SequenceEqual(slCoreRuleSettingsProvider.GetSLCoreRuleSettings())));
}
Expand All @@ -194,7 +198,7 @@ public void Dispose_Initialized_ShutsDownAndDisposesRpc()
SetUpLanguages([], []);

SetUpFullConfiguration(out var rpc);
testSubject.Initialize();
testSubject.InitializeAsync();

rpcManager.ClearReceivedCalls();
testSubject.Dispose();
Expand All @@ -215,7 +219,7 @@ public void Dispose_IgnoresShutdownException()

SetUpFullConfiguration(out var rpc);
rpcManager.When(x => x.Shutdown()).Do(_ => throw new Exception());
testSubject.Initialize();
testSubject.InitializeAsync();

rpcManager.ClearReceivedCalls();
var act = () => testSubject.Dispose();
Expand All @@ -229,7 +233,7 @@ public void Dispose_ConnectionDied_DisposesRpc()
SetUpLanguages([], []);

SetUpFullConfiguration(out var rpc);
testSubject.Initialize();
testSubject.InitializeAsync();

rpcManager.ClearSubstitute();
rpcManager.ClearReceivedCalls();
Expand Down
8 changes: 4 additions & 4 deletions src/SLCore.UnitTests/SLCoreInstanceHandlerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ public void StartInstance_UpdatesCounterAndInitializesOnABackgroundThread()
{
threadHandling.ThrowIfOnUIThread();
factory.CreateInstance();
handle.Initialize();
handle.InitializeAsync();
_ = handle.ShutdownTask;
});
}
Expand Down Expand Up @@ -109,7 +109,7 @@ public async Task StartInstance_InstanceDies_RaisesEventAndResets()
{
threadHandling.ThrowIfOnUIThread();
factory.CreateInstance();
handle.Initialize();
handle.InitializeAsync();
_ = handle.ShutdownTask;
handle.Dispose();
activeConfigScopeTracker.Reset();
Expand Down Expand Up @@ -142,7 +142,7 @@ public void StartInstance_InstanceInitializationThrows_RaisesEventAndResets()
{
var slCoreHandler = CreateTestSubject(out var factory, out var threadHandling, out var logger, out var activeConfigScopeTracker, out _);
SetUpHandleFactory(factory, out var handle, out _);
handle.When(x => x.Initialize()).Do(_ => throw new Exception());
handle.When(x => x.InitializeAsync()).Do(_ => throw new Exception());

var task = slCoreHandler.StartInstanceAsync();

Expand All @@ -157,7 +157,7 @@ public void StartInstance_InstanceInitializationThrows_RaisesEventAndResets()
{
threadHandling.ThrowIfOnUIThread();
factory.CreateInstance();
handle.Initialize();
handle.InitializeAsync();
handle.Dispose();
activeConfigScopeTracker.Reset();
});
Expand Down
4 changes: 4 additions & 0 deletions src/SLCore/ISLCoreInstanceFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ internal class SLCoreInstanceFactory : ISLCoreInstanceFactory
private readonly ISLCoreRuleSettingsProvider slCoreRuleSettingsProvider;
private readonly ISlCoreTelemetryMigrationProvider telemetryMigrationProvider;
private readonly IEsLintBridgeLocator esLintBridgeLocator;
private readonly IFocusOnNewCodeService focusOnNewCodeService;

[ImportingConstructor]
public SLCoreInstanceFactory(
Expand All @@ -70,6 +71,7 @@ public SLCoreInstanceFactory(
ISLCoreRuleSettingsProvider slCoreRuleSettingsProvider,
ISlCoreTelemetryMigrationProvider telemetryMigrationProvider,
IEsLintBridgeLocator esLintBridgeLocator,
IFocusOnNewCodeService focusOnNewCodeService,
IThreadHandling threadHandling)
{
this.slCoreRpcFactory = slCoreRpcFactory;
Expand All @@ -85,6 +87,7 @@ public SLCoreInstanceFactory(
this.slCoreRuleSettingsProvider = slCoreRuleSettingsProvider;
this.telemetryMigrationProvider = telemetryMigrationProvider;
this.esLintBridgeLocator = esLintBridgeLocator;
this.focusOnNewCodeService = focusOnNewCodeService;
this.threadHandling = threadHandling;
}

Expand All @@ -103,5 +106,6 @@ public ISLCoreInstanceHandle CreateInstance() =>
configScopeUpdater,
slCoreRuleSettingsProvider,
telemetryMigrationProvider,
focusOnNewCodeService,
threadHandling);
}
Loading