Skip to content

Commit

Permalink
Update config scope when binding changes (#5241)
Browse files Browse the repository at this point in the history
Part of #5240
  • Loading branch information
georgii-borovinskikh-sonarsource authored Feb 22, 2024
1 parent 9ccf396 commit 61fe405
Show file tree
Hide file tree
Showing 6 changed files with 151 additions and 67 deletions.
62 changes: 50 additions & 12 deletions src/SLCore.UnitTests/State/ActiveConfigScopeTrackerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
using SonarLint.VisualStudio.SLCore.Core;
using SonarLint.VisualStudio.SLCore.Service.Project;
using SonarLint.VisualStudio.SLCore.Service.Project.Models;
using SonarLint.VisualStudio.SLCore.Service.Project.Params;
using SonarLint.VisualStudio.SLCore.State;
using SonarLint.VisualStudio.TestInfrastructure;

Expand Down Expand Up @@ -57,7 +58,7 @@ public async Task SetCurrentConfigScope_SetsUnboundScope()
var threadHandling = new Mock<IThreadHandling>();
ConfigureServiceProvider(out var serviceProvider, out var configScopeService);
ConfigureAsyncLockFactory(out var lockFactory, out var asyncLock, out var lockRelease);
var testSubject = CreateTestSubject(serviceProvider.Object, lockFactory.Object, threadHandling.Object);
var testSubject = CreateTestSubject(serviceProvider.Object, threadHandling.Object, lockFactory.Object);

await testSubject.SetCurrentConfigScopeAsync(configScopeId);

Expand All @@ -76,7 +77,7 @@ public async Task SetCurrentConfigScope_SetsBoundScope()
var threadHandling = new Mock<IThreadHandling>();
ConfigureServiceProvider(out var serviceProvider, out var configScopeService);
ConfigureAsyncLockFactory(out var lockFactory, out var asyncLock, out var lockRelease);
var testSubject = CreateTestSubject(serviceProvider.Object, lockFactory.Object, threadHandling.Object);
var testSubject = CreateTestSubject(serviceProvider.Object, threadHandling.Object, lockFactory.Object);

await testSubject.SetCurrentConfigScopeAsync(configScopeId, connectionId, sonarProjectKey);

Expand All @@ -85,16 +86,40 @@ public async Task SetCurrentConfigScope_SetsBoundScope()
VerifyServiceAddCall(configScopeService, testSubject);
VerifyLockTakenAndReleased(asyncLock, lockRelease);
}

[TestMethod]
public async Task SetCurrentConfigScope_CurrentScopeExists_UpdatesBoundScope()
{
const string configScopeId = "myid";
const string connectionId = "myconid";
const string sonarProjectKey = "projectkey";
var threadHandling = new Mock<IThreadHandling>();
ConfigureServiceProvider(out var serviceProvider, out var configScopeService);
ConfigureAsyncLockFactory(out var lockFactory, out var asyncLock, out var lockRelease);
var testSubject = CreateTestSubject(serviceProvider.Object, threadHandling.Object, lockFactory.Object);
var existingConfigScope = new ConfigurationScopeDto(configScopeId, configScopeId, true, null);
testSubject.currentConfigScope = existingConfigScope;

await testSubject.SetCurrentConfigScopeAsync(configScopeId, connectionId, sonarProjectKey);

testSubject.currentConfigScope.Should().BeEquivalentTo(new ConfigurationScopeDto(configScopeId, configScopeId, true, new BindingConfigurationDto(connectionId, sonarProjectKey)));
testSubject.currentConfigScope.Should().NotBeSameAs(existingConfigScope);
VerifyThreadHandling(threadHandling);
VerifyServiceUpdateCall(configScopeService, testSubject);
VerifyLockTakenAndReleased(asyncLock, lockRelease);
}

[TestMethod]
public async Task SetCurrentConfigScope_ServiceUnavailable_Throws()
{
var threadHandling = new Mock<IThreadHandling>();
ConfigureAsyncLockFactory(out var lockFactory, out _, out _);
var testSubject = CreateTestSubject(Mock.Of<ISLCoreServiceProvider>(), lockFactory.Object, Mock.Of<IThreadHandling>());
var testSubject = CreateTestSubject(Mock.Of<ISLCoreServiceProvider>(), threadHandling.Object, lockFactory.Object);

var act = () => testSubject.SetCurrentConfigScopeAsync("id");

await act.Should().ThrowExactlyAsync<InvalidOperationException>().WithMessage(Strings.ServiceProviderNotInitialized);
VerifyThreadHandling(threadHandling);
}

[TestMethod]
Expand All @@ -104,26 +129,28 @@ public async Task RemoveCurrentConfigScope_RemovesScope()
var threadHandling = new Mock<IThreadHandling>();
ConfigureServiceProvider(out var serviceProvider, out var configScopeService);
ConfigureAsyncLockFactory(out var lockFactory, out var asyncLock, out var lockRelease);
var testSubject = CreateTestSubject(serviceProvider.Object, lockFactory.Object, threadHandling.Object);
var testSubject = CreateTestSubject(serviceProvider.Object, threadHandling.Object, lockFactory.Object);
testSubject.currentConfigScope = new ConfigurationScopeDto(configScopeId, configScopeId, true, null);

await testSubject.RemoveCurrentConfigScopeAsync();

VerifyThreadHandling(threadHandling);
configScopeService.Verify(x => x.DidRemoveConfigurationScopeAsync(It.Is<DidRemoveConfigurationScopeParams>(p => p.removeId == configScopeId)));
VerifyThreadHandling(threadHandling);
VerifyLockTakenAndReleased(asyncLock, lockRelease);
}

[TestMethod]
public async Task RemoveCurrentConfigScope_ServiceUnavailable_Throws()
{
var threadHandling = new Mock<IThreadHandling>();
ConfigureAsyncLockFactory(out var lockFactory, out _, out _);
var testSubject = CreateTestSubject(Mock.Of<ISLCoreServiceProvider>(), lockFactory.Object, Mock.Of<IThreadHandling>());
var testSubject = CreateTestSubject(Mock.Of<ISLCoreServiceProvider>(), threadHandling.Object, lockFactory.Object);
testSubject.currentConfigScope = new ConfigurationScopeDto(default, default, default, default);

var act = () => testSubject.RemoveCurrentConfigScopeAsync();

await act.Should().ThrowExactlyAsync<InvalidOperationException>().WithMessage(Strings.ServiceProviderNotInitialized);
VerifyThreadHandling(threadHandling);
}

[TestMethod]
Expand All @@ -133,7 +160,7 @@ public void GetCurrent_ReturnsUnboundScope()
var threadHandling = new Mock<IThreadHandling>();
ConfigureServiceProvider(out var serviceProvider, out _);
ConfigureAsyncLockFactory(out var lockFactory, out var asyncLock, out var lockRelease);
var testSubject = CreateTestSubject(serviceProvider.Object, lockFactory.Object, threadHandling.Object);
var testSubject = CreateTestSubject(serviceProvider.Object, threadHandling.Object, lockFactory.Object);
testSubject.currentConfigScope = new ConfigurationScopeDto(configScopeId, configScopeId, true, null);

var currentScope = testSubject.Current;
Expand All @@ -152,7 +179,7 @@ public void GetCurrent_ReturnsBoundScope()
var threadHandling = new Mock<IThreadHandling>();
ConfigureServiceProvider(out var serviceProvider, out _);
ConfigureAsyncLockFactory(out var lockFactory, out var asyncLock, out var lockRelease);
var testSubject = CreateTestSubject(serviceProvider.Object, lockFactory.Object, threadHandling.Object);
var testSubject = CreateTestSubject(serviceProvider.Object, threadHandling.Object, lockFactory.Object);
testSubject.currentConfigScope = new ConfigurationScopeDto(configScopeId, configScopeId, true, new BindingConfigurationDto(connectionId, sonarProjectKey));

var currentScope = testSubject.Current;
Expand All @@ -168,15 +195,16 @@ public void Dispose_DisposesLock()
ConfigureServiceProvider(out var serviceProvider, out _);
ConfigureAsyncLockFactory(out var lockFactory, out var asyncLock, out _);

var testSubject = CreateTestSubject(serviceProvider.Object, lockFactory.Object, Mock.Of<IThreadHandling>());
var testSubject = CreateTestSubject(serviceProvider.Object, Mock.Of<IThreadHandling>(), lockFactory.Object);

testSubject.Dispose();
asyncLock.Verify(x => x.Dispose());
}

private static void VerifyThreadHandling(Mock<IThreadHandling> threadHandling)
{
threadHandling.Verify(x => x.ThrowIfOnUIThread());
threadHandling.VerifyNoOtherCalls();
}

private static void VerifyServiceAddCall(Mock<IConfigurationScopeSLCoreService> configScopeService, ActiveConfigScopeTracker testSubject)
Expand All @@ -186,6 +214,16 @@ private static void VerifyServiceAddCall(Mock<IConfigurationScopeSLCoreService>
x.DidAddConfigurationScopesAsync(It.Is<DidAddConfigurationScopesParams>(p =>
p.addedScopes.SequenceEqual(new[] { testSubject.currentConfigScope }))),
Times.Once);
configScopeService.VerifyNoOtherCalls();
}

private static void VerifyServiceUpdateCall(Mock<IConfigurationScopeSLCoreService> configScopeService,
ActiveConfigScopeTracker testSubject)
{
configScopeService
.Verify(x => x.DidUpdateBindingAsync(It.Is<DidUpdateBindingParams>(p =>
p.configScopeId == testSubject.currentConfigScope.id && p.updatedBinding == testSubject.currentConfigScope.binding)), Times.Once);
configScopeService.VerifyNoOtherCalls();
}

private static void VerifyLockTakenSynchronouslyAndReleased(Mock<IAsyncLock> asyncLock, Mock<IReleaseAsyncLock> lockRelease)
Expand Down Expand Up @@ -221,8 +259,8 @@ private static void ConfigureServiceProvider(out Mock<ISLCoreServiceProvider> se
}

private static ActiveConfigScopeTracker CreateTestSubject(ISLCoreServiceProvider slCoreServiceProvider,
IAsyncLockFactory asyncLockFactory,
IThreadHandling threadHandling)
IThreadHandling threadHandling,
IAsyncLockFactory asyncLockFactory)
{
return new ActiveConfigScopeTracker(slCoreServiceProvider, asyncLockFactory, threadHandling);
}
Expand Down
32 changes: 7 additions & 25 deletions src/SLCore/Service/Project/IConfigurationScopeSLCoreService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,10 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Threading.Tasks;
using SonarLint.VisualStudio.SLCore.Core;
using SonarLint.VisualStudio.SLCore.Protocol;
using SonarLint.VisualStudio.SLCore.Service.Project.Models;
using SonarLint.VisualStudio.SLCore.Service.Project.Params;

namespace SonarLint.VisualStudio.SLCore.Service.Project
{
Expand All @@ -41,27 +39,11 @@ public interface IConfigurationScopeSLCoreService : ISLCoreService
/// </summary>
/// <param name="parameters"></param>
Task DidRemoveConfigurationScopeAsync(DidRemoveConfigurationScopeParams parameters);
}

public class DidRemoveConfigurationScopeParams
{
public string removeId { get; }

[ExcludeFromCodeCoverage]
public DidRemoveConfigurationScopeParams(string removeId)
{
this.removeId = removeId;
}
}

public class DidAddConfigurationScopesParams
{
public List<ConfigurationScopeDto> addedScopes { get; }

[ExcludeFromCodeCoverage]
public DidAddConfigurationScopesParams(List<ConfigurationScopeDto> addedScopes)
{
this.addedScopes = addedScopes;
}

/// <summary>
/// Updates binding configuration on an existing configuration scope
/// </summary>
/// <param name="parameters"></param>
Task DidUpdateBindingAsync(DidUpdateBindingParams parameters);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,33 +18,19 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Threading.Tasks;
using SonarLint.VisualStudio.SLCore.Protocol;
using SonarLint.VisualStudio.SLCore.Service.Project.Models;

namespace SonarLint.VisualStudio.SLCore.Service.Project
namespace SonarLint.VisualStudio.SLCore.Service.Project.Params;

public class DidAddConfigurationScopesParams
{
[JsonRpcClassAttribute("configuration")]
public interface IBindingSLCoreService
{
/// <summary>
/// Updates binding configuration on an existing configuration scope
/// </summary>
/// <param name="parameters"></param>
Task DidUpdateBindingAsync(DidUpdateBindingParams parameters);
}
public List<ConfigurationScopeDto> addedScopes { get; }

public class DidUpdateBindingParams
[ExcludeFromCodeCoverage]
public DidAddConfigurationScopesParams(List<ConfigurationScopeDto> addedScopes)
{
public string configScopeId { get; }
public BindingConfigurationDto updatedBinding { get; }

[ExcludeFromCodeCoverage]
public DidUpdateBindingParams(string configScopeId, BindingConfigurationDto updatedBinding)
{
this.configScopeId = configScopeId;
this.updatedBinding = updatedBinding;
}
this.addedScopes = addedScopes;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* SonarLint for Visual Studio
* Copyright (C) 2016-2024 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

using System.Diagnostics.CodeAnalysis;

namespace SonarLint.VisualStudio.SLCore.Service.Project.Params;

public class DidRemoveConfigurationScopeParams
{
public string removeId { get; }

[ExcludeFromCodeCoverage]
public DidRemoveConfigurationScopeParams(string removeId)
{
this.removeId = removeId;
}
}
37 changes: 37 additions & 0 deletions src/SLCore/Service/Project/Params/DidUpdateBindingParams.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* SonarLint for Visual Studio
* Copyright (C) 2016-2024 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

using System.Diagnostics.CodeAnalysis;
using SonarLint.VisualStudio.SLCore.Service.Project.Models;

namespace SonarLint.VisualStudio.SLCore.Service.Project.Params;

public class DidUpdateBindingParams
{
public string configScopeId { get; }
public BindingConfigurationDto updatedBinding { get; }

[ExcludeFromCodeCoverage]
public DidUpdateBindingParams(string configScopeId, BindingConfigurationDto updatedBinding)
{
this.configScopeId = configScopeId;
this.updatedBinding = updatedBinding;
}
}
23 changes: 15 additions & 8 deletions src/SLCore/State/ActiveConfigScopeTracker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
using SonarLint.VisualStudio.SLCore.Core;
using SonarLint.VisualStudio.SLCore.Service.Project;
using SonarLint.VisualStudio.SLCore.Service.Project.Models;
using SonarLint.VisualStudio.SLCore.Service.Project.Params;

namespace SonarLint.VisualStudio.SLCore.State;

Expand Down Expand Up @@ -56,10 +57,9 @@ public ConfigurationScope(string id, string connectionId = null, string sonarPro
[PartCreationPolicy(CreationPolicy.Shared)]
internal sealed class ActiveConfigScopeTracker : IActiveConfigScopeTracker
{
private readonly IAsyncLock asyncLock;

private readonly ISLCoreServiceProvider serviceProvider;
private readonly IThreadHandling threadHandling;
private readonly IAsyncLock asyncLock;

internal /* for testing */ ConfigurationScopeDto currentConfigScope;

Expand Down Expand Up @@ -91,7 +91,7 @@ public ConfigurationScope Current
public async Task SetCurrentConfigScopeAsync(string id, string connectionId = null, string sonarProjectKey = null)
{
threadHandling.ThrowIfOnUIThread();

if (!serviceProvider.TryGetTransientService(out IConfigurationScopeSLCoreService configurationScopeService))
{
throw new InvalidOperationException(Strings.ServiceProviderNotInitialized);
Expand All @@ -104,10 +104,17 @@ public async Task SetCurrentConfigScopeAsync(string id, string connectionId = nu

using (await asyncLock.AcquireAsync())
{
Debug.Assert(currentConfigScope == null);

await configurationScopeService.DidAddConfigurationScopesAsync(
new DidAddConfigurationScopesParams(new List<ConfigurationScopeDto> { configurationScopeDto }));
Debug.Assert(currentConfigScope == null || currentConfigScope.id == id);

if (currentConfigScope?.id == id)
{
await configurationScopeService.DidUpdateBindingAsync(new DidUpdateBindingParams(id, configurationScopeDto.binding));
}
else
{
await configurationScopeService.DidAddConfigurationScopesAsync(
new DidAddConfigurationScopesParams(new List<ConfigurationScopeDto> { configurationScopeDto }));
}
currentConfigScope = configurationScopeDto;
}
}
Expand All @@ -116,7 +123,7 @@ await configurationScopeService.DidAddConfigurationScopesAsync(
public async Task RemoveCurrentConfigScopeAsync()
{
threadHandling.ThrowIfOnUIThread();

if (!serviceProvider.TryGetTransientService(out IConfigurationScopeSLCoreService configurationScopeService))
{
throw new InvalidOperationException(Strings.ServiceProviderNotInitialized);
Expand Down

0 comments on commit 61fe405

Please sign in to comment.