Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update config scope when binding changes #5241

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
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
Loading