Skip to content

Commit

Permalink
Merge pull request #7899 from drewnoakes/dev17.2-fix-source-item-repo…
Browse files Browse the repository at this point in the history
…rting

[17.2] Fix source item reporting
  • Loading branch information
drewnoakes authored Feb 7, 2022
2 parents 935b8ad + 7ae7a63 commit 97aec4c
Show file tree
Hide file tree
Showing 5 changed files with 80 additions and 50 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ public void ApplyProjectEvaluation(
IComparable version = GetConfiguredProjectVersion(update);

ProcessProjectEvaluationHandlers(version, update.Value.ProjectUpdate, state, cancellationToken);
ProcessSourceItemsHandlers(version, update.Value.SourceItemsUpdate, state, cancellationToken);
}

public IEnumerable<string> GetProjectEvaluationRules()
Expand Down Expand Up @@ -194,6 +195,14 @@ private void ProcessProjectEvaluationHandlers(IComparable version, IProjectSubsc

evaluationHandler.Handle(version, projectChange, state, _logger);
}
}
}

private void ProcessSourceItemsHandlers(IComparable version, IProjectSubscriptionUpdate update, ContextState state, CancellationToken cancellationToken)
{
foreach (ExportLifetimeContext<IWorkspaceContextHandler> handler in _handlers)
{
cancellationToken.ThrowIfCancellationRequested();

if (handler.Value is ISourceItemsHandler sourceItemsHandler)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
using System;
using System.ComponentModel.Composition;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.LanguageServices.ProjectSystem;
Expand Down Expand Up @@ -80,9 +79,6 @@ protected override async Task InitializeCoreAsync(CancellationToken cancellation
_evaluationProgressRegistration = _dataProgressTrackerService.RegisterForIntelliSense(this, _project, nameof(WorkspaceProjectContextHostInstance) + ".Evaluation");
_projectBuildProgressRegistration = _dataProgressTrackerService.RegisterForIntelliSense(this, _project, nameof(WorkspaceProjectContextHostInstance) + ".ProjectBuild");

StrongBox<ContextState?> lastEvaluationContextState = new();
StrongBox<ContextState?> lastBuildContextState = new();

_disposables = new DisposableBag
{
_applyChangesToWorkspaceContext,
Expand Down Expand Up @@ -129,7 +125,6 @@ Task OnEvaluationUpdateAsync(IProjectVersionedValue<(ConfiguredProject ActiveCon
return OnProjectChangedAsync(
_evaluationProgressRegistration,
e.Value.ActiveConfiguredProject,
lastEvaluationContextState,
e,
hasChange: static e => e.Value.ProjectUpdate.ProjectChanges.HasChange() || e.Value.SourceItemsUpdate.ProjectChanges.HasChange(),
applyFunc: static (e, applyChangesToWorkspaceContext, contextState, token) => applyChangesToWorkspaceContext.ApplyProjectEvaluation(e.Derive(v => (v.ProjectUpdate, v.SourceItemsUpdate)), contextState, token));
Expand All @@ -140,7 +135,6 @@ Task OnBuildUpdateAsync(IProjectVersionedValue<(ConfiguredProject ActiveConfigur
return OnProjectChangedAsync(
_projectBuildProgressRegistration,
e.Value.ActiveConfiguredProject,
lastBuildContextState,
e,
hasChange: static e => e.Value.BuildUpdate.ProjectChanges.HasChange() || e.Value.CommandLineArgumentsUpdate.IsChanged,
applyFunc: static (e, applyChangesToWorkspaceContext, contextState, token) => applyChangesToWorkspaceContext.ApplyProjectBuild(e.Derive(v => (v.BuildUpdate, v.CommandLineArgumentsUpdate)), contextState, token));
Expand Down Expand Up @@ -196,7 +190,6 @@ public async Task<T> OpenContextForWriteAsync<T>(Func<IWorkspaceProjectContextAc
internal Task OnProjectChangedAsync<T>(
IDataProgressTrackerServiceRegistration registration,
ConfiguredProject activeConfiguredProject,
StrongBox<ContextState?> lastContextState,
IProjectVersionedValue<T> update,
Func<IProjectVersionedValue<T>, bool> hasChange,
Action<IProjectVersionedValue<T>, IApplyChangesToWorkspaceContext, ContextState, CancellationToken> applyFunc)
Expand All @@ -209,9 +202,7 @@ Task ApplyProjectChangesUnderLockAsync(CancellationToken cancellationToken)
Assumes.NotNull(_contextAccessor);
Assumes.NotNull(_applyChangesToWorkspaceContext);

(ContextState contextState, bool stateChanged) = GetContextState(lastContextState);

if (!stateChanged && !hasChange(update))
if (!hasChange(update))
{
// No change since the last update. We must still update operation progress, but can skip creating a batch.
UpdateProgressRegistration();
Expand All @@ -222,6 +213,10 @@ Task ApplyProjectChangesUnderLockAsync(CancellationToken cancellationToken)

async Task ApplyInBatchAsync()
{
ContextState contextState = new(
isActiveEditorContext: _activeWorkspaceProjectContextTracker.IsActiveEditorContext(_contextAccessor.ContextId),
isActiveConfiguration: activeConfiguredProject == _project);

IWorkspaceProjectContext context = _contextAccessor.Context;

context.StartBatch();
Expand All @@ -238,27 +233,6 @@ async Task ApplyInBatchAsync()
}
}

(ContextState ContextState, bool StateChanged) GetContextState(StrongBox<ContextState?> lastContextState)
{
bool isActiveConfiguration = activeConfiguredProject == _project;
bool isActiveEditorContext = _activeWorkspaceProjectContextTracker.IsActiveEditorContext(_contextAccessor.ContextId);

ContextState newState = new(isActiveEditorContext, isActiveConfiguration);

bool stateChanged = false;

if (lastContextState.Value is null ||
lastContextState.Value.Value.IsActiveConfiguration != newState.IsActiveConfiguration ||
lastContextState.Value.Value.IsActiveEditorContext != newState.IsActiveEditorContext)
{
// The state has changed
lastContextState.Value = newState;
stateChanged = true;
}

return (newState, stateChanged);
}

void UpdateProgressRegistration()
{
// Notify operation progress that we've now processed these versions of our input, if they are
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Licensed to the .NET Foundation under one or more agreements. The .NET Foundation licenses this file to you under the MIT license. See the LICENSE.md file in the project root for more information.

using System;
using System.Collections.Immutable;
using Microsoft.VisualStudio.ProjectSystem.VS;
using Moq;

namespace Microsoft.VisualStudio.ProjectSystem.LanguageServices
{
internal static class ISourceItemsHandlerFactory
{
public static ISourceItemsHandler ImplementHandle(Action<IComparable, IImmutableDictionary<string, IProjectChangeDescription>, ContextState, IProjectDiagnosticOutputService> action)
{
var mock = new Mock<ISourceItemsHandler>();

mock.Setup(h => h.Handle(It.IsAny<IComparable>(), It.IsAny<IImmutableDictionary<string, IProjectChangeDescription>>(), It.IsAny<ContextState>(), It.IsAny<IProjectDiagnosticOutputService>()))
.Callback(action);

return mock.Object;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,7 @@ public void ApplyProjectBuild_WhenNoCompilerCommandLineArgsRuleChanges_Throws()
}

[Fact]
public void ApplyProjectEvaluation_CallsHandler()
public void ApplyProjectEvaluation_ProjectUpdate_CallsHandler()
{
(IComparable version, IProjectChangeDescription description, ContextState state, IProjectDiagnosticOutputService logger) result = default;

Expand Down Expand Up @@ -336,6 +336,42 @@ public void ApplyProjectEvaluation_CallsHandler()
Assert.NotNull(result.logger);
}

[Fact]
public void ApplyProjectEvaluation_SourceItems_CallsHandler()
{
(IComparable version, IImmutableDictionary<string, IProjectChangeDescription> description, ContextState state, IProjectDiagnosticOutputService logger) result = default;

var handler = ISourceItemsHandlerFactory.ImplementHandle((version, description, state, logger) =>
{
result = (version, description, state, logger);
});

var applyChangesToWorkspace = CreateInitializedInstance(handlers: new[] { handler });

var projectUpdate = IProjectSubscriptionUpdateFactory.CreateEmpty();
var sourceItemsUpdate = IProjectSubscriptionUpdateFactory.FromJson(
@"{
""ProjectChanges"": {
""RuleName"": {
""Difference"": {
""AnyChanges"": true
},
}
}
}");
int version = 2;

var update = IProjectVersionedValueFactory.Create((projectUpdate, sourceItemsUpdate), ProjectDataSources.ConfiguredProjectVersion, version);

applyChangesToWorkspace.ApplyProjectEvaluation(update, new ContextState(isActiveEditorContext: true, isActiveConfiguration: true), CancellationToken.None);

Assert.Equal(version, result.version);
Assert.NotNull(result.description);
Assert.True(result.state.IsActiveEditorContext);
Assert.True(result.state.IsActiveConfiguration);
Assert.NotNull(result.logger);
}

[Fact]
public void ApplyProjectBuild_ParseCommandLineAndCallsHandler()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
using System;
using System.Collections.Immutable;
using System.ComponentModel.Composition;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.LanguageServices.ProjectSystem;
Expand Down Expand Up @@ -146,14 +145,12 @@ internal async Task OnProjectChangedAsync_WhenProjectUnloaded_TriggersCancellati
var registration = IDataProgressTrackerServiceRegistrationFactory.Create();
var activeConfiguredProject = ConfiguredProjectFactory.Create();
var update = IProjectVersionedValueFactory.CreateEmpty();
var lastContextState = new StrongBox<ContextState?>();

await Assert.ThrowsAsync<OperationCanceledException>(() =>
{
return instance.OnProjectChangedAsync(
registration,
activeConfiguredProject,
lastContextState,
update,
hasChange: _ => true,
applyFunc: (_, _, _, token) =>
Expand All @@ -173,14 +170,12 @@ internal async Task OnProjectChangedAsync_WhenInstanceDisposed_TriggersCancellat
var registration = IDataProgressTrackerServiceRegistrationFactory.Create();
var activeConfiguredProject = ConfiguredProjectFactory.Create();
var update = IProjectVersionedValueFactory.CreateEmpty();
var lastContextState = new StrongBox<ContextState?>();

await Assert.ThrowsAsync<OperationCanceledException>(() =>
{
return instance.OnProjectChangedAsync(
registration,
activeConfiguredProject,
lastContextState,
update,
hasChange: _ => true,
applyFunc: (_, _, state, token) =>
Expand All @@ -207,43 +202,39 @@ internal async Task OnProjectChangedAsync_CallsApplyFuncOnlyWhenChangeExists()
var update1 = IProjectVersionedValueFactory.Create(versions1);
var update2 = IProjectVersionedValueFactory.Create(versions2);
var update3 = IProjectVersionedValueFactory.Create(versions3);
var lastContextState = new StrongBox<ContextState?>();
var callCount = 0;

// Apply func will be called here as the last context state differs
// Apply func not called as no change
await instance.OnProjectChangedAsync(
registration,
activeConfiguredProject,
lastContextState,
update1,
hasChange: _ => false, // no change
applyFunc: (_, _, state, token) => callCount++);

Assert.Equal(1, callCount);
Assert.Equal(0, callCount);
Assert.Same(versions1, seenVersions);

// Apply func will NOT be called here as the context state is unchanged, and we claim to change to other data items
// Apply func will be called as hasChange returns true, despite the context state being unchanged
await instance.OnProjectChangedAsync(
registration,
activeConfiguredProject,
lastContextState,
update2,
hasChange: _ => false, // no change
hasChange: _ => true, // change
applyFunc: (_, _, state, token) => callCount++);

Assert.Equal(1, callCount);
Assert.Same(versions2, seenVersions);

// Apply func will be called as hasChange returns true, despite the context state being unchanged
// Apply func not called as no change
await instance.OnProjectChangedAsync(
registration,
activeConfiguredProject,
lastContextState,
update3,
hasChange: _ => true, // change
hasChange: _ => false, // no change
applyFunc: (_, _, state, token) => callCount++);

Assert.Equal(2, callCount);
Assert.Equal(1, callCount);
Assert.Same(versions3, seenVersions);
}

Expand All @@ -260,14 +251,12 @@ internal async Task OnProjectChangedAsync_RespectsIsActiveContext(bool isActiveE

var registration = IDataProgressTrackerServiceRegistrationFactory.Create();
var update = IProjectVersionedValueFactory.CreateEmpty();
var lastContextState = new StrongBox<ContextState?>();

ContextState? observedState = null;

await instance.OnProjectChangedAsync(
registration,
activeConfiguredProject,
lastContextState,
update,
hasChange: _ => true,
applyFunc: (_, _, state, token) => observedState = state);
Expand Down

0 comments on commit 97aec4c

Please sign in to comment.