зеркало из https://github.com/dotnet/razor.git
Clean up and 'fix' Live Share (#9916)
This change cleans up the `Microsoft.VisualStudio.LiveShare.Razor`
project and its unit tests. In addition, it marks several types as
public to avoid `TypeLoadExceptions` when Live Share is connected.
Essentially, any services that are proxied by live share from the guest
to the host need to have public contracts; otherwise, Live Share's RPC
infrastructure fails to create the proxy. I introduced this failure
seven months ago with
4ffcd3f78e
,
but it seems that the failure was never reported.
This commit is contained in:
Коммит
dec4b332f9
|
@ -1,24 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT license. See License.txt in the project root for license information.
|
||||
|
||||
using System.ComponentModel.Composition;
|
||||
|
||||
namespace Microsoft.VisualStudio.LiveShare.Razor.Guest;
|
||||
|
||||
[Export(typeof(LiveShareSessionAccessor))]
|
||||
internal class DefaultLiveShareSessionAccessor : LiveShareSessionAccessor
|
||||
{
|
||||
private CollaborationSession? _currentSession;
|
||||
private bool _guestSessionIsActive;
|
||||
|
||||
// We have a separate IsGuestSessionActive to avoid loading LiveShare dlls unnecessarily.
|
||||
public override bool IsGuestSessionActive => _guestSessionIsActive;
|
||||
|
||||
public override CollaborationSession? Session => _currentSession;
|
||||
|
||||
public void SetSession(CollaborationSession? session)
|
||||
{
|
||||
_guestSessionIsActive = session is not null;
|
||||
_currentSession = session;
|
||||
}
|
||||
}
|
|
@ -1,59 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT license. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Threading;
|
||||
using Microsoft.VisualStudio.Threading;
|
||||
|
||||
namespace Microsoft.VisualStudio.LiveShare.Razor.Guest;
|
||||
|
||||
[Export(typeof(ProxyAccessor))]
|
||||
internal class DefaultProxyAccessor : ProxyAccessor
|
||||
{
|
||||
private readonly LiveShareSessionAccessor _liveShareSessionAccessor;
|
||||
private readonly JoinableTaskFactory _joinableTaskFactory;
|
||||
private IProjectHierarchyProxy? _projectHierarchyProxy;
|
||||
|
||||
[ImportingConstructor]
|
||||
public DefaultProxyAccessor(
|
||||
LiveShareSessionAccessor liveShareSessionAccessor,
|
||||
JoinableTaskContext joinableTaskContext)
|
||||
{
|
||||
if (liveShareSessionAccessor is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(liveShareSessionAccessor));
|
||||
}
|
||||
|
||||
if (joinableTaskContext is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(joinableTaskContext));
|
||||
}
|
||||
|
||||
_liveShareSessionAccessor = liveShareSessionAccessor;
|
||||
_joinableTaskFactory = joinableTaskContext.Factory;
|
||||
}
|
||||
|
||||
// Testing constructor
|
||||
#pragma warning disable CS8618 // Non-nullable variable must contain a non-null value when exiting constructor. Consider declaring it as nullable.
|
||||
private protected DefaultProxyAccessor()
|
||||
#pragma warning restore CS8618 // Non-nullable variable must contain a non-null value when exiting constructor. Consider declaring it as nullable.
|
||||
{
|
||||
}
|
||||
|
||||
public override IProjectHierarchyProxy GetProjectHierarchyProxy()
|
||||
{
|
||||
_projectHierarchyProxy ??= CreateServiceProxy<IProjectHierarchyProxy>();
|
||||
|
||||
return _projectHierarchyProxy;
|
||||
}
|
||||
|
||||
// Internal virtual for testing
|
||||
internal virtual TProxy CreateServiceProxy<TProxy>() where TProxy : class
|
||||
{
|
||||
Assumes.NotNull(_liveShareSessionAccessor.Session);
|
||||
#pragma warning disable VSTHRD110 // Observe result of async calls
|
||||
return _joinableTaskFactory.Run(() => _liveShareSessionAccessor.Session.GetRemoteServiceAsync<TProxy>(typeof(TProxy).Name, CancellationToken.None));
|
||||
#pragma warning restore VSTHRD110 // Observe result of async calls
|
||||
}
|
||||
}
|
|
@ -17,13 +17,13 @@ namespace Microsoft.VisualStudio.LiveShare.Razor.Guest;
|
|||
internal class GuestProjectPathProvider(
|
||||
JoinableTaskContext joinableTaskContext,
|
||||
ITextDocumentFactoryService textDocumentFactory,
|
||||
ProxyAccessor proxyAccessor,
|
||||
LiveShareSessionAccessor liveShareSessionAccessor) : ILiveShareProjectPathProvider
|
||||
IProxyAccessor proxyAccessor,
|
||||
ILiveShareSessionAccessor liveShareSessionAccessor) : ILiveShareProjectPathProvider
|
||||
{
|
||||
private readonly JoinableTaskFactory _joinableTaskFactory = joinableTaskContext.Factory;
|
||||
private readonly ITextDocumentFactoryService _textDocumentFactory = textDocumentFactory;
|
||||
private readonly ProxyAccessor _proxyAccessor = proxyAccessor;
|
||||
private readonly LiveShareSessionAccessor _liveShareSessionAccessor = liveShareSessionAccessor;
|
||||
private readonly IProxyAccessor _proxyAccessor = proxyAccessor;
|
||||
private readonly ILiveShareSessionAccessor _liveShareSessionAccessor = liveShareSessionAccessor;
|
||||
|
||||
public bool TryGetProjectPath(ITextBuffer textBuffer, [NotNullWhen(returnValue: true)] out string? filePath)
|
||||
{
|
||||
|
@ -51,8 +51,7 @@ internal class GuestProjectPathProvider(
|
|||
return true;
|
||||
}
|
||||
|
||||
// Internal virtual for testing
|
||||
internal virtual Uri? GetHostProjectPath(ITextDocument textDocument)
|
||||
private Uri? GetHostProjectPath(ITextDocument textDocument)
|
||||
{
|
||||
Assumes.NotNull(_liveShareSessionAccessor.Session);
|
||||
|
||||
|
@ -75,6 +74,8 @@ internal class GuestProjectPathProvider(
|
|||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private string ResolveGuestPath(Uri hostProjectPath)
|
||||
{
|
||||
return _liveShareSessionAccessor.Session!.ConvertSharedUriToLocalPath(hostProjectPath);
|
||||
Assumes.NotNull(_liveShareSessionAccessor.Session);
|
||||
|
||||
return _liveShareSessionAccessor.Session.ConvertSharedUriToLocalPath(hostProjectPath);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT license. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.VisualStudio.LiveShare.Razor.Guest;
|
||||
|
||||
internal interface ILiveShareSessionAccessor
|
||||
{
|
||||
CollaborationSession? Session { get; }
|
||||
bool IsGuestSessionActive { get; }
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT license. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.VisualStudio.LiveShare.Razor.Guest;
|
||||
|
||||
internal interface IProxyAccessor
|
||||
{
|
||||
IProjectHierarchyProxy GetProjectHierarchyProxy();
|
||||
}
|
|
@ -1,11 +1,23 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT license. See License.txt in the project root for license information.
|
||||
|
||||
using System.ComponentModel.Composition;
|
||||
|
||||
namespace Microsoft.VisualStudio.LiveShare.Razor.Guest;
|
||||
|
||||
internal abstract class LiveShareSessionAccessor
|
||||
[Export(typeof(ILiveShareSessionAccessor))]
|
||||
internal class LiveShareSessionAccessor : ILiveShareSessionAccessor
|
||||
{
|
||||
public abstract CollaborationSession? Session { get; }
|
||||
private CollaborationSession? _currentSession;
|
||||
private bool _guestSessionIsActive;
|
||||
|
||||
public abstract bool IsGuestSessionActive { get; }
|
||||
// We have a separate IsGuestSessionActive to avoid loading LiveShare dlls unnecessarily.
|
||||
public bool IsGuestSessionActive => _guestSessionIsActive;
|
||||
public CollaborationSession? Session => _currentSession;
|
||||
|
||||
public void SetSession(CollaborationSession? session)
|
||||
{
|
||||
_guestSessionIsActive = session is not null;
|
||||
_currentSession = session;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,30 +13,20 @@ using IAsyncDisposable = Microsoft.VisualStudio.Threading.IAsyncDisposable;
|
|||
|
||||
namespace Microsoft.VisualStudio.LiveShare.Razor.Guest;
|
||||
|
||||
internal class ProjectSnapshotSynchronizationService : ICollaborationService, IAsyncDisposable, System.IAsyncDisposable
|
||||
internal class ProjectSnapshotSynchronizationService(
|
||||
CollaborationSession sessionContext,
|
||||
IProjectSnapshotManagerProxy hostProjectManagerProxy,
|
||||
IProjectSnapshotManagerAccessor projectManagerAccessor,
|
||||
ProjectSnapshotManagerDispatcher dispatcher,
|
||||
IErrorReporter errorReporter,
|
||||
JoinableTaskFactory jtf) : ICollaborationService, IAsyncDisposable, System.IAsyncDisposable
|
||||
{
|
||||
private readonly JoinableTaskFactory _joinableTaskFactory;
|
||||
private readonly CollaborationSession _sessionContext;
|
||||
private readonly IProjectSnapshotManagerProxy _hostProjectManagerProxy;
|
||||
private readonly IProjectSnapshotManagerAccessor _projectManagerAccessor;
|
||||
private readonly IErrorReporter _errorReporter;
|
||||
private readonly ProjectSnapshotManagerDispatcher _dispatcher;
|
||||
|
||||
public ProjectSnapshotSynchronizationService(
|
||||
JoinableTaskFactory joinableTaskFactory,
|
||||
CollaborationSession sessionContext,
|
||||
IProjectSnapshotManagerProxy hostProjectManagerProxy,
|
||||
IProjectSnapshotManagerAccessor projectManagerAccessor,
|
||||
IErrorReporter errorReporter,
|
||||
ProjectSnapshotManagerDispatcher dispatcher)
|
||||
{
|
||||
_joinableTaskFactory = joinableTaskFactory;
|
||||
_sessionContext = sessionContext;
|
||||
_hostProjectManagerProxy = hostProjectManagerProxy;
|
||||
_projectManagerAccessor = projectManagerAccessor;
|
||||
_errorReporter = errorReporter;
|
||||
_dispatcher = dispatcher;
|
||||
}
|
||||
private readonly JoinableTaskFactory _jtf = jtf;
|
||||
private readonly CollaborationSession _sessionContext = sessionContext;
|
||||
private readonly IProjectSnapshotManagerProxy _hostProjectManagerProxy = hostProjectManagerProxy;
|
||||
private readonly IProjectSnapshotManagerAccessor _projectManagerAccessor = projectManagerAccessor;
|
||||
private readonly IErrorReporter _errorReporter = errorReporter;
|
||||
private readonly ProjectSnapshotManagerDispatcher _dispatcher = dispatcher;
|
||||
|
||||
public async Task InitializeAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
|
@ -153,7 +143,7 @@ internal class ProjectSnapshotSynchronizationService : ICollaborationService, IA
|
|||
throw new ArgumentNullException(nameof(args));
|
||||
}
|
||||
|
||||
_joinableTaskFactory.Run(async () =>
|
||||
_jtf.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
|
|
|
@ -15,10 +15,10 @@ namespace Microsoft.VisualStudio.LiveShare.Razor.Guest;
|
|||
[ExportCollaborationService(typeof(ProjectSnapshotSynchronizationService), Scope = SessionScope.Guest)]
|
||||
[method: ImportingConstructor]
|
||||
internal class ProjectSnapshotSynchronizationServiceFactory(
|
||||
JoinableTaskContext joinableTaskContext,
|
||||
IProjectSnapshotManagerAccessor projectManagerAccessor,
|
||||
ProjectSnapshotManagerDispatcher dispatcher,
|
||||
IErrorReporter errorReporter,
|
||||
ProjectSnapshotManagerDispatcher dispatcher) : ICollaborationServiceFactory
|
||||
JoinableTaskContext joinableTaskContext) : ICollaborationServiceFactory
|
||||
{
|
||||
public async Task<ICollaborationService> CreateServiceAsync(CollaborationSession sessionContext, CancellationToken cancellationToken)
|
||||
{
|
||||
|
@ -29,12 +29,12 @@ internal class ProjectSnapshotSynchronizationServiceFactory(
|
|||
|
||||
var projectSnapshotManagerProxy = await sessionContext.GetRemoteServiceAsync<IProjectSnapshotManagerProxy>(typeof(IProjectSnapshotManagerProxy).Name, cancellationToken);
|
||||
var synchronizationService = new ProjectSnapshotSynchronizationService(
|
||||
joinableTaskContext.Factory,
|
||||
sessionContext,
|
||||
projectSnapshotManagerProxy,
|
||||
projectManagerAccessor,
|
||||
dispatcher,
|
||||
errorReporter,
|
||||
dispatcher);
|
||||
joinableTaskContext.Factory);
|
||||
|
||||
await synchronizationService.InitializeAsync(cancellationToken);
|
||||
|
||||
|
|
|
@ -1,9 +1,31 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT license. See License.txt in the project root for license information.
|
||||
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Threading;
|
||||
using Microsoft.VisualStudio.Threading;
|
||||
|
||||
namespace Microsoft.VisualStudio.LiveShare.Razor.Guest;
|
||||
|
||||
internal abstract class ProxyAccessor
|
||||
[Export(typeof(IProxyAccessor))]
|
||||
[method: ImportingConstructor]
|
||||
internal class ProxyAccessor(
|
||||
ILiveShareSessionAccessor liveShareSessionAccessor,
|
||||
JoinableTaskContext joinableTaskContext) : IProxyAccessor
|
||||
{
|
||||
public abstract IProjectHierarchyProxy GetProjectHierarchyProxy();
|
||||
private readonly ILiveShareSessionAccessor _liveShareSessionAccessor = liveShareSessionAccessor;
|
||||
private readonly JoinableTaskFactory _jtf = joinableTaskContext.Factory;
|
||||
|
||||
private IProjectHierarchyProxy? _projectHierarchyProxy;
|
||||
|
||||
public IProjectHierarchyProxy GetProjectHierarchyProxy()
|
||||
=> _projectHierarchyProxy ??= CreateServiceProxy<IProjectHierarchyProxy>();
|
||||
|
||||
private TProxy CreateServiceProxy<TProxy>() where TProxy : class
|
||||
{
|
||||
Assumes.NotNull(_liveShareSessionAccessor.Session);
|
||||
|
||||
return _jtf.Run(
|
||||
() => _liveShareSessionAccessor.Session.GetRemoteServiceAsync<TProxy>(typeof(TProxy).Name, CancellationToken.None));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,35 +12,19 @@ using Microsoft.CodeAnalysis.Razor;
|
|||
namespace Microsoft.VisualStudio.LiveShare.Razor.Guest;
|
||||
|
||||
[ExportCollaborationService(typeof(SessionActiveDetector), Scope = SessionScope.Guest)]
|
||||
internal class RazorGuestInitializationService : ICollaborationServiceFactory
|
||||
[method: ImportingConstructor]
|
||||
internal class RazorGuestInitializationService(
|
||||
[Import(typeof(ILiveShareSessionAccessor))] LiveShareSessionAccessor sessionAccessor) : ICollaborationServiceFactory
|
||||
{
|
||||
private const string ViewImportsFileName = "_ViewImports.cshtml";
|
||||
private readonly DefaultLiveShareSessionAccessor _sessionAccessor;
|
||||
private readonly LiveShareSessionAccessor _sessionAccessor = sessionAccessor;
|
||||
|
||||
// Internal for testing
|
||||
internal Task? _viewImportsCopyTask;
|
||||
|
||||
[ImportingConstructor]
|
||||
public RazorGuestInitializationService([Import(typeof(LiveShareSessionAccessor))] DefaultLiveShareSessionAccessor sessionAccessor)
|
||||
{
|
||||
if (sessionAccessor is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(sessionAccessor));
|
||||
}
|
||||
|
||||
_sessionAccessor = sessionAccessor;
|
||||
}
|
||||
private Task? _viewImportsCopyTask;
|
||||
|
||||
public Task<ICollaborationService> CreateServiceAsync(CollaborationSession sessionContext, CancellationToken cancellationToken)
|
||||
{
|
||||
if (sessionContext is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(sessionContext));
|
||||
}
|
||||
|
||||
#pragma warning disable CA2000 // Dispose objects before losing scope
|
||||
var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
|
||||
#pragma warning restore CA2000 // Dispose objects before losing scope
|
||||
|
||||
_viewImportsCopyTask = EnsureViewImportsCopiedAsync(sessionContext, cts.Token);
|
||||
|
||||
_sessionAccessor.SetSession(sessionContext);
|
||||
|
@ -99,6 +83,14 @@ internal class RazorGuestInitializationService : ICollaborationServiceFactory
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal TestAccessor GetTestAccessor()
|
||||
=> new(this);
|
||||
|
||||
internal sealed class TestAccessor(RazorGuestInitializationService instance)
|
||||
{
|
||||
public Task? ViewImportsCopyTask => instance._viewImportsCopyTask;
|
||||
}
|
||||
}
|
||||
|
||||
internal class SessionActiveDetector(Action onDispose) : ICollaborationService, IDisposable
|
||||
|
|
|
@ -1,45 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT license. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CodeAnalysis.Razor;
|
||||
using Microsoft.VisualStudio.Threading;
|
||||
|
||||
namespace Microsoft.VisualStudio.LiveShare.Razor.Host;
|
||||
|
||||
[ExportCollaborationService(
|
||||
typeof(IProjectHierarchyProxy),
|
||||
Name = nameof(IProjectHierarchyProxy),
|
||||
Scope = SessionScope.Host,
|
||||
Role = ServiceRole.RemoteService)]
|
||||
internal class DefaultProjectHierarchyProxyFactory : ICollaborationServiceFactory
|
||||
{
|
||||
private readonly JoinableTaskContext _joinableTaskContext;
|
||||
|
||||
[ImportingConstructor]
|
||||
public DefaultProjectHierarchyProxyFactory(
|
||||
ProjectSnapshotManagerDispatcher projectSnapshotManagerDispatcher,
|
||||
JoinableTaskContext joinableTaskContext)
|
||||
{
|
||||
if (joinableTaskContext is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(joinableTaskContext));
|
||||
}
|
||||
|
||||
_joinableTaskContext = joinableTaskContext;
|
||||
}
|
||||
|
||||
public Task<ICollaborationService> CreateServiceAsync(CollaborationSession session, CancellationToken cancellationToken)
|
||||
{
|
||||
if (session is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(session));
|
||||
}
|
||||
|
||||
var service = new DefaultProjectHierarchyProxy(session, _joinableTaskContext.Factory);
|
||||
return Task.FromResult<ICollaborationService>(service);
|
||||
}
|
||||
}
|
|
@ -10,31 +10,17 @@ using Microsoft.VisualStudio.Threading;
|
|||
|
||||
namespace Microsoft.VisualStudio.LiveShare.Razor.Host;
|
||||
|
||||
internal class DefaultProjectHierarchyProxy : IProjectHierarchyProxy, ICollaborationService
|
||||
internal class ProjectHierarchyProxy(
|
||||
CollaborationSession session,
|
||||
IServiceProvider serviceProvider,
|
||||
JoinableTaskFactory jtf) : IProjectHierarchyProxy, ICollaborationService
|
||||
{
|
||||
private readonly CollaborationSession _session;
|
||||
private readonly CollaborationSession _session = session;
|
||||
private readonly IServiceProvider _serviceProvider = serviceProvider;
|
||||
private readonly JoinableTaskFactory _jtf = jtf;
|
||||
|
||||
private readonly JoinableTaskFactory _joinableTaskFactory;
|
||||
private IVsUIShellOpenDocument? _openDocumentShell;
|
||||
|
||||
public DefaultProjectHierarchyProxy(
|
||||
CollaborationSession session,
|
||||
JoinableTaskFactory joinableTaskFactory)
|
||||
{
|
||||
if (session is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(session));
|
||||
}
|
||||
|
||||
if (joinableTaskFactory is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(joinableTaskFactory));
|
||||
}
|
||||
|
||||
_session = session;
|
||||
_joinableTaskFactory = joinableTaskFactory;
|
||||
}
|
||||
|
||||
public async Task<Uri?> GetProjectPathAsync(Uri documentFilePath, CancellationToken cancellationToken)
|
||||
{
|
||||
if (documentFilePath is null)
|
||||
|
@ -42,13 +28,13 @@ internal class DefaultProjectHierarchyProxy : IProjectHierarchyProxy, ICollabora
|
|||
throw new ArgumentNullException(nameof(documentFilePath));
|
||||
}
|
||||
|
||||
await _joinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
|
||||
await _jtf.SwitchToMainThreadAsync(cancellationToken);
|
||||
|
||||
_openDocumentShell ??= _serviceProvider.GetService(typeof(SVsUIShellOpenDocument)) as IVsUIShellOpenDocument;
|
||||
Assumes.Present(_openDocumentShell);
|
||||
|
||||
#pragma warning disable VSSDK006 // Check services exist
|
||||
_openDocumentShell ??= ServiceProvider.GlobalProvider.GetService(typeof(SVsUIShellOpenDocument)) as IVsUIShellOpenDocument;
|
||||
#pragma warning restore VSSDK006 // Check services exist
|
||||
var hostDocumentFilePath = _session.ConvertSharedUriToLocalPath(documentFilePath);
|
||||
var hr = _openDocumentShell!.IsDocumentInAProject(hostDocumentFilePath, out var hierarchy, out _, out _, out _);
|
||||
var hr = _openDocumentShell.IsDocumentInAProject(hostDocumentFilePath, out var hierarchy, out _, out _, out _);
|
||||
if (ErrorHandler.Succeeded(hr) && hierarchy != null)
|
||||
{
|
||||
ErrorHandler.ThrowOnFailure(((IVsProject)hierarchy).GetMkDocument((uint)VSConstants.VSITEMID.Root, out var path), VSConstants.E_NOTIMPL);
|
|
@ -0,0 +1,28 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT license. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.VisualStudio.Shell;
|
||||
using Microsoft.VisualStudio.Threading;
|
||||
|
||||
namespace Microsoft.VisualStudio.LiveShare.Razor.Host;
|
||||
|
||||
[ExportCollaborationService(
|
||||
typeof(IProjectHierarchyProxy),
|
||||
Name = nameof(IProjectHierarchyProxy),
|
||||
Scope = SessionScope.Host,
|
||||
Role = ServiceRole.RemoteService)]
|
||||
[method: ImportingConstructor]
|
||||
internal class ProjectHierarchyProxyFactory(
|
||||
[Import(typeof(SVsServiceProvider))] IServiceProvider serviceProvider,
|
||||
JoinableTaskContext joinableTaskContext) : ICollaborationServiceFactory
|
||||
{
|
||||
public Task<ICollaborationService> CreateServiceAsync(CollaborationSession session, CancellationToken cancellationToken)
|
||||
{
|
||||
var service = new ProjectHierarchyProxy(session, serviceProvider, joinableTaskContext.Factory);
|
||||
return Task.FromResult<ICollaborationService>(service);
|
||||
}
|
||||
}
|
|
@ -13,49 +13,28 @@ using Microsoft.VisualStudio.Threading;
|
|||
|
||||
namespace Microsoft.VisualStudio.LiveShare.Razor.Host;
|
||||
|
||||
internal class DefaultProjectSnapshotManagerProxy : IProjectSnapshotManagerProxy, ICollaborationService, IDisposable
|
||||
internal class ProjectSnapshotManagerProxy : IProjectSnapshotManagerProxy, ICollaborationService, IDisposable
|
||||
{
|
||||
private readonly CollaborationSession _session;
|
||||
private readonly ProjectSnapshotManagerDispatcher _dispatcher;
|
||||
private readonly IProjectSnapshotManager _projectSnapshotManager;
|
||||
private readonly JoinableTaskFactory _joinableTaskFactory;
|
||||
private readonly JoinableTaskFactory _jtf;
|
||||
private readonly AsyncSemaphore _latestStateSemaphore;
|
||||
private bool _disposed;
|
||||
private ProjectSnapshotManagerProxyState? _latestState;
|
||||
|
||||
// Internal for testing
|
||||
internal JoinableTask? _processingChangedEventTestTask;
|
||||
private JoinableTask? _processingChangedEventTestTask;
|
||||
|
||||
public DefaultProjectSnapshotManagerProxy(
|
||||
public ProjectSnapshotManagerProxy(
|
||||
CollaborationSession session,
|
||||
ProjectSnapshotManagerDispatcher dispatcher,
|
||||
IProjectSnapshotManager projectSnapshotManager,
|
||||
JoinableTaskFactory joinableTaskFactory)
|
||||
ProjectSnapshotManagerDispatcher dispatcher,
|
||||
JoinableTaskFactory jtf)
|
||||
{
|
||||
if (session is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(session));
|
||||
}
|
||||
|
||||
if (dispatcher is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(dispatcher));
|
||||
}
|
||||
|
||||
if (projectSnapshotManager is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(projectSnapshotManager));
|
||||
}
|
||||
|
||||
if (joinableTaskFactory is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(joinableTaskFactory));
|
||||
}
|
||||
|
||||
_session = session;
|
||||
_dispatcher = dispatcher;
|
||||
_projectSnapshotManager = projectSnapshotManager;
|
||||
_joinableTaskFactory = joinableTaskFactory;
|
||||
_jtf = jtf;
|
||||
|
||||
_latestStateSemaphore = new AsyncSemaphore(initialCount: 1);
|
||||
_projectSnapshotManager.Changed += ProjectSnapshotManager_Changed;
|
||||
|
@ -81,8 +60,6 @@ internal class DefaultProjectSnapshotManagerProxy : IProjectSnapshotManagerProxy
|
|||
|
||||
public void Dispose()
|
||||
{
|
||||
_dispatcher.AssertDispatcherThread();
|
||||
|
||||
_projectSnapshotManager.Changed -= ProjectSnapshotManager_Changed;
|
||||
_latestStateSemaphore.Dispose();
|
||||
_disposed = true;
|
||||
|
@ -91,9 +68,9 @@ internal class DefaultProjectSnapshotManagerProxy : IProjectSnapshotManagerProxy
|
|||
// Internal for testing
|
||||
internal async Task<IReadOnlyList<IProjectSnapshot>> GetLatestProjectsAsync()
|
||||
{
|
||||
if (!_joinableTaskFactory.Context.IsOnMainThread)
|
||||
if (!_jtf.Context.IsOnMainThread)
|
||||
{
|
||||
await _joinableTaskFactory.SwitchToMainThreadAsync(CancellationToken.None);
|
||||
await _jtf.SwitchToMainThreadAsync(CancellationToken.None);
|
||||
}
|
||||
|
||||
return _projectSnapshotManager.GetProjects();
|
||||
|
@ -148,11 +125,11 @@ internal class DefaultProjectSnapshotManagerProxy : IProjectSnapshotManagerProxy
|
|||
return;
|
||||
}
|
||||
|
||||
_processingChangedEventTestTask = _joinableTaskFactory.RunAsync(async () =>
|
||||
_processingChangedEventTestTask = _jtf.RunAsync(async () =>
|
||||
{
|
||||
var projects = await GetLatestProjectsAsync();
|
||||
|
||||
await _joinableTaskFactory.SwitchToMainThreadAsync();
|
||||
await _jtf.SwitchToMainThreadAsync();
|
||||
|
||||
var oldProjectProxy = await ConvertToProxyAsync(args.Older).ConfigureAwait(false);
|
||||
var newProjectProxy = await ConvertToProxyAsync(args.Newer).ConfigureAwait(false);
|
||||
|
@ -173,4 +150,12 @@ internal class DefaultProjectSnapshotManagerProxy : IProjectSnapshotManagerProxy
|
|||
|
||||
Changed?.Invoke(this, args);
|
||||
}
|
||||
|
||||
internal TestAccessor GetTestAccessor()
|
||||
=> new(this);
|
||||
|
||||
internal sealed class TestAccessor(ProjectSnapshotManagerProxy instance)
|
||||
{
|
||||
public JoinableTask? ProcessingChangedEventTestTask => instance._processingChangedEventTestTask;
|
||||
}
|
||||
}
|
|
@ -1,7 +1,6 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT license. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
@ -19,23 +18,18 @@ namespace Microsoft.VisualStudio.LiveShare.Razor.Host;
|
|||
Scope = SessionScope.Host,
|
||||
Role = ServiceRole.RemoteService)]
|
||||
[method: ImportingConstructor]
|
||||
internal class DefaultProjectSnapshotManagerProxyFactory(
|
||||
internal class ProjectSnapshotManagerProxyFactory(
|
||||
IProjectSnapshotManagerAccessor projectManagerAccessor,
|
||||
ProjectSnapshotManagerDispatcher dispatcher,
|
||||
JoinableTaskContext joinableTaskContext,
|
||||
IProjectSnapshotManagerAccessor projectManagerAccessor) : ICollaborationServiceFactory
|
||||
JoinableTaskContext joinableTaskContext) : ICollaborationServiceFactory
|
||||
{
|
||||
public Task<ICollaborationService> CreateServiceAsync(CollaborationSession session, CancellationToken cancellationToken)
|
||||
{
|
||||
if (session is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(session));
|
||||
}
|
||||
|
||||
var serializer = (JsonSerializer)session.GetService(typeof(JsonSerializer));
|
||||
serializer.Converters.RegisterRazorLiveShareConverters();
|
||||
|
||||
var service = new DefaultProjectSnapshotManagerProxy(
|
||||
session, dispatcher, projectManagerAccessor.Instance, joinableTaskContext.Factory);
|
||||
var service = new ProjectSnapshotManagerProxy(
|
||||
session, projectManagerAccessor.Instance, dispatcher, joinableTaskContext.Factory);
|
||||
return Task.FromResult<ICollaborationService>(service);
|
||||
}
|
||||
}
|
|
@ -7,7 +7,8 @@ using System.Threading.Tasks;
|
|||
|
||||
namespace Microsoft.VisualStudio.LiveShare.Razor;
|
||||
|
||||
internal interface IProjectHierarchyProxy
|
||||
// This type must be a public interface in order to to be implemented as an RPC proxy by live share.
|
||||
public interface IProjectHierarchyProxy
|
||||
{
|
||||
Task<Uri?> GetProjectPathAsync(Uri documentFilePath, CancellationToken cancellationToken);
|
||||
}
|
||||
|
|
|
@ -7,9 +7,10 @@ using System.Threading.Tasks;
|
|||
|
||||
namespace Microsoft.VisualStudio.LiveShare.Razor;
|
||||
|
||||
internal interface IProjectSnapshotManagerProxy
|
||||
// This type must be a public interface in order to to be implemented as an RPC proxy by live share.
|
||||
public interface IProjectSnapshotManagerProxy
|
||||
{
|
||||
event EventHandler<ProjectChangeEventProxyArgs>? Changed;
|
||||
event EventHandler<ProjectChangeEventProxyArgs> Changed;
|
||||
|
||||
Task<ProjectSnapshotManagerProxyState> GetProjectManagerStateAsync(CancellationToken cancellationToken);
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ using System.Threading.Tasks;
|
|||
|
||||
namespace Microsoft.VisualStudio.LiveShare.Razor;
|
||||
|
||||
// This type must be a public interface in order to properly advertise itself as part of the LiveShare ICollaborationService infrastructure.
|
||||
// This type must be a public interface in order to to be implemented as an RPC proxy by live share.
|
||||
public interface IRemoteHierarchyService : ICollaborationService
|
||||
{
|
||||
public Task<bool> HasCapabilityAsync(Uri pathOfFileInProject, string capability, CancellationToken cancellationToken);
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT license. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Threading;
|
||||
using Microsoft.VisualStudio.Editor.Razor;
|
||||
|
@ -11,29 +10,13 @@ using Microsoft.VisualStudio.Threading;
|
|||
namespace Microsoft.VisualStudio.LiveShare.Razor;
|
||||
|
||||
[Export(typeof(ProjectCapabilityResolver))]
|
||||
internal class LiveShareProjectCapabilityResolver : ProjectCapabilityResolver
|
||||
[method: ImportingConstructor]
|
||||
internal class LiveShareProjectCapabilityResolver(
|
||||
ILiveShareSessionAccessor sessionAccessor,
|
||||
JoinableTaskContext joinableTaskContext) : ProjectCapabilityResolver
|
||||
{
|
||||
private readonly LiveShareSessionAccessor _sessionAccessor;
|
||||
private readonly JoinableTaskFactory _joinableTaskFactory;
|
||||
|
||||
[ImportingConstructor]
|
||||
public LiveShareProjectCapabilityResolver(
|
||||
LiveShareSessionAccessor sessionAccessor,
|
||||
JoinableTaskContext joinableTaskContext)
|
||||
{
|
||||
if (sessionAccessor is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(sessionAccessor));
|
||||
}
|
||||
|
||||
if (joinableTaskContext is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(joinableTaskContext));
|
||||
}
|
||||
|
||||
_sessionAccessor = sessionAccessor;
|
||||
_joinableTaskFactory = joinableTaskContext.Factory;
|
||||
}
|
||||
private readonly ILiveShareSessionAccessor _sessionAccessor = sessionAccessor;
|
||||
private readonly JoinableTaskFactory _joinableTaskFactory = joinableTaskContext.Factory;
|
||||
|
||||
public override bool HasCapability(object project, string capability)
|
||||
{
|
||||
|
|
|
@ -2,11 +2,20 @@
|
|||
// Licensed under the MIT license. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Razor;
|
||||
|
||||
namespace Microsoft.VisualStudio.LiveShare.Razor;
|
||||
|
||||
internal sealed class ProjectChangeEventProxyArgs : EventArgs
|
||||
// This type must be public because it is exposed by a public interface that is implemented as
|
||||
// an RPC proxy by live share.
|
||||
public sealed class ProjectChangeEventProxyArgs : EventArgs
|
||||
{
|
||||
public ProjectSnapshotHandleProxy? Older { get; }
|
||||
public ProjectSnapshotHandleProxy? Newer { get; }
|
||||
public ProjectProxyChangeKind Kind { get; }
|
||||
public Uri ProjectFilePath { get; }
|
||||
public Uri IntermediateOutputPath { get; }
|
||||
|
||||
public ProjectChangeEventProxyArgs(ProjectSnapshotHandleProxy? older, ProjectSnapshotHandleProxy? newer, ProjectProxyChangeKind kind)
|
||||
{
|
||||
if (older is null && newer is null)
|
||||
|
@ -18,17 +27,7 @@ internal sealed class ProjectChangeEventProxyArgs : EventArgs
|
|||
Newer = newer;
|
||||
Kind = kind;
|
||||
|
||||
ProjectFilePath = older?.FilePath ?? newer!.FilePath;
|
||||
IntermediateOutputPath = older?.IntermediateOutputPath ?? newer!.IntermediateOutputPath;
|
||||
ProjectFilePath = older?.FilePath ?? newer.AssumeNotNull().FilePath;
|
||||
IntermediateOutputPath = older?.IntermediateOutputPath ?? newer.AssumeNotNull().IntermediateOutputPath;
|
||||
}
|
||||
|
||||
public ProjectSnapshotHandleProxy? Older { get; }
|
||||
|
||||
public ProjectSnapshotHandleProxy? Newer { get; }
|
||||
|
||||
public Uri ProjectFilePath { get; }
|
||||
|
||||
public Uri IntermediateOutputPath { get; }
|
||||
|
||||
public ProjectProxyChangeKind Kind { get; }
|
||||
}
|
||||
|
|
|
@ -3,7 +3,9 @@
|
|||
|
||||
namespace Microsoft.VisualStudio.LiveShare.Razor;
|
||||
|
||||
internal enum ProjectProxyChangeKind
|
||||
// This type must be public because it is exposed by a public interface that is implemented as
|
||||
// an RPC proxy by live share.
|
||||
public enum ProjectProxyChangeKind
|
||||
{
|
||||
ProjectAdded,
|
||||
ProjectRemoved,
|
||||
|
|
|
@ -7,20 +7,23 @@ using Microsoft.AspNetCore.Razor.ProjectSystem;
|
|||
|
||||
namespace Microsoft.VisualStudio.LiveShare.Razor;
|
||||
|
||||
internal sealed class ProjectSnapshotHandleProxy
|
||||
// This type must be public because it is exposed by a public interface that is implemented as
|
||||
// an RPC proxy by live share. However, its properties and constructor are intentionally internal
|
||||
// because they expose internal compiler APIs.
|
||||
public sealed class ProjectSnapshotHandleProxy
|
||||
{
|
||||
public Uri FilePath { get; }
|
||||
public Uri IntermediateOutputPath { get; }
|
||||
public RazorConfiguration Configuration { get; }
|
||||
public string? RootNamespace { get; }
|
||||
public ProjectWorkspaceState? ProjectWorkspaceState { get; }
|
||||
internal Uri FilePath { get; }
|
||||
internal Uri IntermediateOutputPath { get; }
|
||||
internal RazorConfiguration Configuration { get; }
|
||||
internal string? RootNamespace { get; }
|
||||
internal ProjectWorkspaceState ProjectWorkspaceState { get; }
|
||||
|
||||
public ProjectSnapshotHandleProxy(
|
||||
internal ProjectSnapshotHandleProxy(
|
||||
Uri filePath,
|
||||
Uri intermediateOutputPath,
|
||||
RazorConfiguration configuration,
|
||||
string? rootNamespace,
|
||||
ProjectWorkspaceState? projectWorkspaceState)
|
||||
ProjectWorkspaceState projectWorkspaceState)
|
||||
{
|
||||
FilePath = filePath ?? throw new ArgumentNullException(nameof(filePath));
|
||||
IntermediateOutputPath = intermediateOutputPath ?? throw new ArgumentNullException(nameof(intermediateOutputPath));
|
||||
|
|
|
@ -6,7 +6,9 @@ using System.Collections.Generic;
|
|||
|
||||
namespace Microsoft.VisualStudio.LiveShare.Razor;
|
||||
|
||||
internal sealed class ProjectSnapshotManagerProxyState(IReadOnlyList<ProjectSnapshotHandleProxy> projectHandles)
|
||||
// This type must be public because it is exposed by a public interface that is implemented as
|
||||
// an RPC proxy by live share.
|
||||
public sealed class ProjectSnapshotManagerProxyState(IReadOnlyList<ProjectSnapshotHandleProxy> projectHandles)
|
||||
{
|
||||
public IReadOnlyList<ProjectSnapshotHandleProxy> ProjectHandles { get; } = projectHandles ?? throw new ArgumentNullException(nameof(projectHandles));
|
||||
}
|
||||
|
|
|
@ -1,3 +1,23 @@
|
|||
#nullable enable
|
||||
Microsoft.VisualStudio.LiveShare.Razor.IProjectHierarchyProxy
|
||||
Microsoft.VisualStudio.LiveShare.Razor.IProjectHierarchyProxy.GetProjectPathAsync(System.Uri! documentFilePath, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task<System.Uri?>!
|
||||
Microsoft.VisualStudio.LiveShare.Razor.IProjectSnapshotManagerProxy
|
||||
Microsoft.VisualStudio.LiveShare.Razor.IProjectSnapshotManagerProxy.Changed -> System.EventHandler<Microsoft.VisualStudio.LiveShare.Razor.ProjectChangeEventProxyArgs!>!
|
||||
Microsoft.VisualStudio.LiveShare.Razor.IProjectSnapshotManagerProxy.GetProjectManagerStateAsync(System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task<Microsoft.VisualStudio.LiveShare.Razor.ProjectSnapshotManagerProxyState!>!
|
||||
Microsoft.VisualStudio.LiveShare.Razor.IRemoteHierarchyService
|
||||
Microsoft.VisualStudio.LiveShare.Razor.IRemoteHierarchyService.HasCapabilityAsync(System.Uri! pathOfFileInProject, string! capability, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task<bool>!
|
||||
Microsoft.VisualStudio.LiveShare.Razor.ProjectChangeEventProxyArgs
|
||||
Microsoft.VisualStudio.LiveShare.Razor.ProjectChangeEventProxyArgs.IntermediateOutputPath.get -> System.Uri!
|
||||
Microsoft.VisualStudio.LiveShare.Razor.ProjectChangeEventProxyArgs.Kind.get -> Microsoft.VisualStudio.LiveShare.Razor.ProjectProxyChangeKind
|
||||
Microsoft.VisualStudio.LiveShare.Razor.ProjectChangeEventProxyArgs.Newer.get -> Microsoft.VisualStudio.LiveShare.Razor.ProjectSnapshotHandleProxy?
|
||||
Microsoft.VisualStudio.LiveShare.Razor.ProjectChangeEventProxyArgs.Older.get -> Microsoft.VisualStudio.LiveShare.Razor.ProjectSnapshotHandleProxy?
|
||||
Microsoft.VisualStudio.LiveShare.Razor.ProjectChangeEventProxyArgs.ProjectChangeEventProxyArgs(Microsoft.VisualStudio.LiveShare.Razor.ProjectSnapshotHandleProxy? older, Microsoft.VisualStudio.LiveShare.Razor.ProjectSnapshotHandleProxy? newer, Microsoft.VisualStudio.LiveShare.Razor.ProjectProxyChangeKind kind) -> void
|
||||
Microsoft.VisualStudio.LiveShare.Razor.ProjectChangeEventProxyArgs.ProjectFilePath.get -> System.Uri!
|
||||
Microsoft.VisualStudio.LiveShare.Razor.ProjectProxyChangeKind
|
||||
Microsoft.VisualStudio.LiveShare.Razor.ProjectProxyChangeKind.ProjectAdded = 0 -> Microsoft.VisualStudio.LiveShare.Razor.ProjectProxyChangeKind
|
||||
Microsoft.VisualStudio.LiveShare.Razor.ProjectProxyChangeKind.ProjectChanged = 2 -> Microsoft.VisualStudio.LiveShare.Razor.ProjectProxyChangeKind
|
||||
Microsoft.VisualStudio.LiveShare.Razor.ProjectProxyChangeKind.ProjectRemoved = 1 -> Microsoft.VisualStudio.LiveShare.Razor.ProjectProxyChangeKind
|
||||
Microsoft.VisualStudio.LiveShare.Razor.ProjectSnapshotHandleProxy
|
||||
Microsoft.VisualStudio.LiveShare.Razor.ProjectSnapshotManagerProxyState
|
||||
Microsoft.VisualStudio.LiveShare.Razor.ProjectSnapshotManagerProxyState.ProjectHandles.get -> System.Collections.Generic.IReadOnlyList<Microsoft.VisualStudio.LiveShare.Razor.ProjectSnapshotHandleProxy!>!
|
||||
Microsoft.VisualStudio.LiveShare.Razor.ProjectSnapshotManagerProxyState.ProjectSnapshotManagerProxyState(System.Collections.Generic.IReadOnlyList<Microsoft.VisualStudio.LiveShare.Razor.ProjectSnapshotHandleProxy!>! projectHandles) -> void
|
||||
|
|
|
@ -10,26 +10,10 @@ using Microsoft.VisualStudio.Threading;
|
|||
|
||||
namespace Microsoft.VisualStudio.LiveShare.Razor;
|
||||
|
||||
internal class RemoteHierarchyService : IRemoteHierarchyService
|
||||
internal class RemoteHierarchyService(CollaborationSession session, JoinableTaskFactory jtf) : IRemoteHierarchyService
|
||||
{
|
||||
private readonly CollaborationSession _session;
|
||||
private readonly JoinableTaskFactory _joinableTaskFactory;
|
||||
|
||||
internal RemoteHierarchyService(CollaborationSession session, JoinableTaskFactory joinableTaskFactory)
|
||||
{
|
||||
if (session is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(session));
|
||||
}
|
||||
|
||||
if (joinableTaskFactory is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(joinableTaskFactory));
|
||||
}
|
||||
|
||||
_session = session;
|
||||
_joinableTaskFactory = joinableTaskFactory;
|
||||
}
|
||||
private readonly CollaborationSession _session = session;
|
||||
private readonly JoinableTaskFactory _jtf = jtf;
|
||||
|
||||
public async Task<bool> HasCapabilityAsync(Uri pathOfFileInProject, string capability, CancellationToken cancellationToken)
|
||||
{
|
||||
|
@ -43,7 +27,7 @@ internal class RemoteHierarchyService : IRemoteHierarchyService
|
|||
throw new ArgumentNullException(nameof(capability));
|
||||
}
|
||||
|
||||
await _joinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
|
||||
await _jtf.SwitchToMainThreadAsync(cancellationToken);
|
||||
|
||||
var hostPathOfFileInProject = _session.ConvertSharedUriToLocalPath(pathOfFileInProject);
|
||||
if (ServiceProvider.GlobalProvider.GetService(typeof(SVsUIShellOpenDocument)) is not IVsUIShellOpenDocument vsUIShellOpenDocument)
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT license. See License.txt in the project root for license information.
|
||||
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.VisualStudio.Shell;
|
||||
using Microsoft.VisualStudio.Threading;
|
||||
using Task = System.Threading.Tasks.Task;
|
||||
|
||||
namespace Microsoft.VisualStudio.LiveShare.Razor;
|
||||
|
@ -17,10 +18,11 @@ namespace Microsoft.VisualStudio.LiveShare.Razor;
|
|||
Name = nameof(IRemoteHierarchyService),
|
||||
Scope = SessionScope.Host,
|
||||
Role = ServiceRole.RemoteService)]
|
||||
internal sealed class RemoteHierarchyServiceFactory : ICollaborationServiceFactory
|
||||
[method: ImportingConstructor]
|
||||
internal sealed class RemoteHierarchyServiceFactory(JoinableTaskContext joinableTaskContext) : ICollaborationServiceFactory
|
||||
{
|
||||
public Task<ICollaborationService> CreateServiceAsync(CollaborationSession session, CancellationToken cancellationToken)
|
||||
{
|
||||
return Task.FromResult<ICollaborationService>(new RemoteHierarchyService(session, ThreadHelper.JoinableTaskFactory));
|
||||
return Task.FromResult<ICollaborationService>(new RemoteHierarchyService(session, joinableTaskContext.Factory));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT license. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNetCore.Razor.ProjectSystem;
|
||||
using Microsoft.AspNetCore.Razor.Serialization.Json;
|
||||
|
||||
namespace Microsoft.VisualStudio.LiveShare.Razor.Serialization;
|
||||
|
@ -15,7 +16,7 @@ internal class ProjectSnapshotHandleProxyJsonConverter : ObjectJsonConverter<Pro
|
|||
var intermediateOutputPath = reader.ReadNonNullUri(nameof(ProjectSnapshotHandleProxy.IntermediateOutputPath));
|
||||
var configuration = reader.ReadNonNullObject(nameof(ProjectSnapshotHandleProxy.Configuration), ObjectReaders.ReadConfigurationFromProperties);
|
||||
var rootNamespace = reader.ReadStringOrNull(nameof(ProjectSnapshotHandleProxy.RootNamespace));
|
||||
var projectWorkspaceState = reader.ReadObjectOrNull(nameof(ProjectSnapshotHandleProxy.ProjectWorkspaceState), ObjectReaders.ReadProjectWorkspaceStateFromProperties);
|
||||
var projectWorkspaceState = reader.ReadObjectOrNull(nameof(ProjectSnapshotHandleProxy.ProjectWorkspaceState), ObjectReaders.ReadProjectWorkspaceStateFromProperties) ?? ProjectWorkspaceState.Default;
|
||||
|
||||
return new(filePath, intermediateOutputPath, configuration, rootNamespace, projectWorkspaceState);
|
||||
}
|
||||
|
|
|
@ -1,50 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT license. See License.txt in the project root for license information.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Razor.Test.Common;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace Microsoft.VisualStudio.LiveShare.Razor.Guest;
|
||||
|
||||
public class DefaultProxyAccessorTest(ITestOutputHelper testOutput) : ToolingTestBase(testOutput)
|
||||
{
|
||||
[Fact]
|
||||
public void GetProjectHierarchyProxy_Caches()
|
||||
{
|
||||
// Arrange
|
||||
var proxy = Mock.Of<IProjectHierarchyProxy>(MockBehavior.Strict);
|
||||
var proxyAccessor = new TestProxyAccessor<IProjectHierarchyProxy>(proxy);
|
||||
|
||||
// Act
|
||||
var proxy1 = proxyAccessor.GetProjectHierarchyProxy();
|
||||
var proxy2 = proxyAccessor.GetProjectHierarchyProxy();
|
||||
|
||||
// Assert
|
||||
Assert.Same(proxy1, proxy2);
|
||||
}
|
||||
|
||||
private class TestProxyAccessor<TTestProxy> : DefaultProxyAccessor where TTestProxy : class
|
||||
{
|
||||
private readonly TTestProxy _proxy;
|
||||
|
||||
public TestProxyAccessor(TTestProxy proxy)
|
||||
{
|
||||
_proxy = proxy;
|
||||
}
|
||||
|
||||
internal override TProxy CreateServiceProxy<TProxy>()
|
||||
{
|
||||
if (typeof(TProxy) == typeof(TTestProxy))
|
||||
{
|
||||
return _proxy as TProxy;
|
||||
}
|
||||
|
||||
throw new InvalidOperationException("The proxy accessor was called with unexpected arguments.");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,47 +1,33 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT license. See License.txt in the project root for license information.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Razor.Test.Common;
|
||||
using Microsoft.VisualStudio.LiveShare.Razor.Test;
|
||||
using Microsoft.VisualStudio.Text;
|
||||
using Microsoft.VisualStudio.Threading;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace Microsoft.VisualStudio.LiveShare.Razor.Guest;
|
||||
|
||||
public class GuestProjectPathProviderTest : ToolingTestBase
|
||||
public class GuestProjectPathProviderTest(ITestOutputHelper testOutput) : ToolingTestBase(testOutput)
|
||||
{
|
||||
private readonly LiveShareSessionAccessor _sessionAccessor;
|
||||
|
||||
public GuestProjectPathProviderTest(ITestOutputHelper testOutput)
|
||||
: base(testOutput)
|
||||
{
|
||||
var collabSession = new TestCollaborationSession(isHost: false);
|
||||
_sessionAccessor = Mock.Of<LiveShareSessionAccessor>(
|
||||
a => a.IsGuestSessionActive == true && a.Session == collabSession,
|
||||
MockBehavior.Strict);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[UIFact]
|
||||
public void TryGetProjectPath_GuestSessionNotActive_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
var sessionAccessor = Mock.Of<LiveShareSessionAccessor>(accessor => accessor.IsGuestSessionActive == false, MockBehavior.Strict);
|
||||
var textBuffer = Mock.Of<ITextBuffer>(MockBehavior.Strict);
|
||||
var textDocument = Mock.Of<ITextDocument>(MockBehavior.Strict);
|
||||
var textDocumentFactory = Mock.Of<ITextDocumentFactoryService>(factory => factory.TryGetTextDocument(textBuffer, out textDocument) == true, MockBehavior.Strict);
|
||||
var projectPathProvider = new TestGuestProjectPathProvider(
|
||||
new Uri("vsls:/path/project.csproj"),
|
||||
var sessionAccessor = StrictMock.Of<ILiveShareSessionAccessor>(a =>
|
||||
a.IsGuestSessionActive == false);
|
||||
var textBuffer = StrictMock.Of<ITextBuffer>();
|
||||
var textDocument = StrictMock.Of<ITextDocument>();
|
||||
var textDocumentFactory = StrictMock.Of<ITextDocumentFactoryService>(f =>
|
||||
f.TryGetTextDocument(textBuffer, out textDocument) == true);
|
||||
|
||||
var projectPathProvider = new GuestProjectPathProvider(
|
||||
JoinableTaskContext,
|
||||
textDocumentFactory,
|
||||
Mock.Of<ProxyAccessor>(MockBehavior.Strict),
|
||||
StrictMock.Of<IProxyAccessor>(),
|
||||
sessionAccessor);
|
||||
|
||||
// Act
|
||||
|
@ -52,18 +38,27 @@ public class GuestProjectPathProviderTest : ToolingTestBase
|
|||
Assert.Null(filePath);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[UIFact]
|
||||
public void TryGetProjectPath_NoTextDocument_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
var textBuffer = Mock.Of<ITextBuffer>(MockBehavior.Strict);
|
||||
var textDocumentFactoryService = new Mock<ITextDocumentFactoryService>(MockBehavior.Strict);
|
||||
textDocumentFactoryService.Setup(s => s.TryGetTextDocument(It.IsAny<ITextBuffer>(), out It.Ref<ITextDocument>.IsAny)).Returns(false);
|
||||
var collaborationSession = StrictMock.Of<CollaborationSession>();
|
||||
|
||||
var sessionAccessor = StrictMock.Of<ILiveShareSessionAccessor>(a =>
|
||||
a.IsGuestSessionActive == true &&
|
||||
a.Session == collaborationSession);
|
||||
|
||||
var textBuffer = StrictMock.Of<ITextBuffer>();
|
||||
var textDocumentFactoryServiceMock = new StrictMock<ITextDocumentFactoryService>();
|
||||
textDocumentFactoryServiceMock
|
||||
.Setup(s => s.TryGetTextDocument(It.IsAny<ITextBuffer>(), out It.Ref<ITextDocument>.IsAny))
|
||||
.Returns(false);
|
||||
|
||||
var projectPathProvider = new GuestProjectPathProvider(
|
||||
JoinableTaskContext,
|
||||
textDocumentFactoryService.Object,
|
||||
Mock.Of<ProxyAccessor>(MockBehavior.Strict),
|
||||
_sessionAccessor);
|
||||
textDocumentFactoryServiceMock.Object,
|
||||
StrictMock.Of<IProxyAccessor>(),
|
||||
sessionAccessor);
|
||||
|
||||
// Act
|
||||
var result = projectPathProvider.TryGetProjectPath(textBuffer, out var filePath);
|
||||
|
@ -73,19 +68,46 @@ public class GuestProjectPathProviderTest : ToolingTestBase
|
|||
Assert.Null(filePath);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[UIFact]
|
||||
public void TryGetProjectPath_NullHostProjectPath_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
var textBuffer = Mock.Of<ITextBuffer>(MockBehavior.Strict);
|
||||
var textDocument = Mock.Of<ITextDocument>(MockBehavior.Strict);
|
||||
var textDocumentFactory = Mock.Of<ITextDocumentFactoryService>(factory => factory.TryGetTextDocument(textBuffer, out textDocument) == true, MockBehavior.Strict);
|
||||
var projectPathProvider = new TestGuestProjectPathProvider(
|
||||
null,
|
||||
var documentFilePath = "/path/to/document.razor";
|
||||
var documentFilePathUri = new Uri("vsls:" + documentFilePath);
|
||||
var projectFilePath = "/path/to/project.razor";
|
||||
var projectFilePathUri = new Uri("vsls:" + projectFilePath);
|
||||
|
||||
var collaborationSessionMock = new StrictMock<CollaborationSession>();
|
||||
collaborationSessionMock
|
||||
.Setup(x => x.ConvertLocalPathToSharedUri(documentFilePath))
|
||||
.Returns(documentFilePathUri);
|
||||
collaborationSessionMock
|
||||
.Setup(x => x.ConvertSharedUriToLocalPath(projectFilePathUri))
|
||||
.Returns(projectFilePath);
|
||||
|
||||
var sessionAccessor = StrictMock.Of<ILiveShareSessionAccessor>(a =>
|
||||
a.IsGuestSessionActive == true &&
|
||||
a.Session == collaborationSessionMock.Object);
|
||||
|
||||
var textBuffer = StrictMock.Of<ITextBuffer>();
|
||||
var textDocument = StrictMock.Of<ITextDocument>(d =>
|
||||
d.FilePath == documentFilePath);
|
||||
var textDocumentFactory = StrictMock.Of<ITextDocumentFactoryService>(f =>
|
||||
f.TryGetTextDocument(textBuffer, out textDocument) == true);
|
||||
|
||||
var proxy = new StrictMock<IProjectHierarchyProxy>();
|
||||
proxy
|
||||
.Setup(x => x.GetProjectPathAsync(documentFilePathUri, CancellationToken.None))
|
||||
.ReturnsAsync((Uri?)null);
|
||||
|
||||
var proxyAccessor = StrictMock.Of<IProxyAccessor>(a =>
|
||||
a.GetProjectHierarchyProxy() == proxy.Object);
|
||||
|
||||
var projectPathProvider = new GuestProjectPathProvider(
|
||||
JoinableTaskContext,
|
||||
textDocumentFactory,
|
||||
Mock.Of<ProxyAccessor>(MockBehavior.Strict),
|
||||
_sessionAccessor);
|
||||
proxyAccessor,
|
||||
sessionAccessor);
|
||||
|
||||
// Act
|
||||
var result = projectPathProvider.TryGetProjectPath(textBuffer, out var filePath);
|
||||
|
@ -95,76 +117,55 @@ public class GuestProjectPathProviderTest : ToolingTestBase
|
|||
Assert.Null(filePath);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[UIFact]
|
||||
public void TryGetProjectPath_ValidHostProjectPath_ReturnsTrueWithGuestNormalizedPath()
|
||||
{
|
||||
// Arrange
|
||||
var textBuffer = Mock.Of<ITextBuffer>(MockBehavior.Strict);
|
||||
var textDocument = Mock.Of<ITextDocument>(MockBehavior.Strict);
|
||||
var textDocumentFactory = Mock.Of<ITextDocumentFactoryService>(factory => factory.TryGetTextDocument(textBuffer, out textDocument) == true, MockBehavior.Strict);
|
||||
var expectedProjectPath = "/guest/path/project.csproj";
|
||||
var projectPathProvider = new TestGuestProjectPathProvider(
|
||||
new Uri("vsls:/path/project.csproj"),
|
||||
var documentFilePath = "/path/to/document.razor";
|
||||
var documentFilePathUri = new Uri("vsls:" + documentFilePath);
|
||||
var projectFilePath = "/path/to/project.csproj";
|
||||
var projectFilePathUri = new Uri("vsls:" + projectFilePath);
|
||||
|
||||
var collaborationSessionMock = new StrictMock<CollaborationSession>();
|
||||
collaborationSessionMock
|
||||
.Setup(x => x.ConvertLocalPathToSharedUri(documentFilePath))
|
||||
.Returns(documentFilePathUri);
|
||||
collaborationSessionMock
|
||||
.Setup(x => x.ConvertSharedUriToLocalPath(projectFilePathUri))
|
||||
.Returns(projectFilePath);
|
||||
|
||||
var sessionAccessor = StrictMock.Of<ILiveShareSessionAccessor>(a =>
|
||||
a.IsGuestSessionActive == true &&
|
||||
a.Session == collaborationSessionMock.Object);
|
||||
|
||||
var textBuffer = StrictMock.Of<ITextBuffer>();
|
||||
var textDocument = StrictMock.Of<ITextDocument>(d =>
|
||||
d.FilePath == documentFilePath);
|
||||
var textDocumentFactory = StrictMock.Of<ITextDocumentFactoryService>(f =>
|
||||
f.TryGetTextDocument(textBuffer, out textDocument) == true);
|
||||
|
||||
var proxy = new StrictMock<IProjectHierarchyProxy>();
|
||||
proxy
|
||||
.Setup(x => x.GetProjectPathAsync(documentFilePathUri, CancellationToken.None))
|
||||
.ReturnsAsync(projectFilePathUri)
|
||||
.Verifiable();
|
||||
|
||||
var proxyAccessor = StrictMock.Of<IProxyAccessor>(a =>
|
||||
a.GetProjectHierarchyProxy() == proxy.Object);
|
||||
|
||||
var projectPathProvider = new GuestProjectPathProvider(
|
||||
JoinableTaskContext,
|
||||
textDocumentFactory,
|
||||
Mock.Of<ProxyAccessor>(MockBehavior.Strict),
|
||||
_sessionAccessor);
|
||||
proxyAccessor,
|
||||
sessionAccessor);
|
||||
|
||||
// Act
|
||||
var result = projectPathProvider.TryGetProjectPath(textBuffer, out var filePath);
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
Assert.Equal(expectedProjectPath, filePath);
|
||||
}
|
||||
Assert.Equal(projectFilePath, filePath);
|
||||
|
||||
[Fact]
|
||||
public void GetHostProjectPath_AsksProxyForProjectPathAsync()
|
||||
{
|
||||
// Arrange
|
||||
var expectedGuestFilePath = "/guest/path/index.cshtml";
|
||||
var expectedHostFilePath = new Uri("vsls:/path/index.cshtml");
|
||||
var expectedHostProjectPath = new Uri("vsls:/path/project.csproj");
|
||||
var collabSession = new TestCollaborationSession(isHost: true);
|
||||
var sessionAccessor = Mock.Of<LiveShareSessionAccessor>(accessor => accessor.IsGuestSessionActive == true && accessor.Session == collabSession, MockBehavior.Strict);
|
||||
|
||||
var proxy = Mock.Of<IProjectHierarchyProxy>(
|
||||
p => p.GetProjectPathAsync(expectedHostFilePath, CancellationToken.None) == Task.FromResult(expectedHostProjectPath),
|
||||
MockBehavior.Strict);
|
||||
var proxyAccessor = Mock.Of<ProxyAccessor>(
|
||||
accessor => accessor.GetProjectHierarchyProxy() == proxy,
|
||||
MockBehavior.Strict);
|
||||
var textDocument = Mock.Of<ITextDocument>(
|
||||
document => document.FilePath == expectedGuestFilePath,
|
||||
MockBehavior.Strict);
|
||||
var projectPathProvider = new GuestProjectPathProvider(
|
||||
JoinableTaskContext,
|
||||
Mock.Of<ITextDocumentFactoryService>(MockBehavior.Strict),
|
||||
proxyAccessor,
|
||||
sessionAccessor);
|
||||
|
||||
// Act
|
||||
var hostProjectPath = projectPathProvider.GetHostProjectPath(textDocument);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedHostProjectPath, hostProjectPath);
|
||||
}
|
||||
|
||||
private class TestGuestProjectPathProvider : GuestProjectPathProvider
|
||||
{
|
||||
private readonly Uri _hostProjectPath;
|
||||
|
||||
public TestGuestProjectPathProvider(
|
||||
Uri hostProjectPath,
|
||||
JoinableTaskContext joinableTaskContext,
|
||||
ITextDocumentFactoryService textDocumentFactory,
|
||||
ProxyAccessor proxyAccessor,
|
||||
LiveShareSessionAccessor liveShareSessionAccessor)
|
||||
: base(joinableTaskContext, textDocumentFactory, proxyAccessor, liveShareSessionAccessor)
|
||||
{
|
||||
_hostProjectPath = hostProjectPath;
|
||||
}
|
||||
|
||||
internal override Uri GetHostProjectPath(ITextDocument textDocument) => _hostProjectPath;
|
||||
proxy.Verify();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,11 +3,11 @@
|
|||
|
||||
using System;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.AspNetCore.Razor.ProjectSystem;
|
||||
using Microsoft.AspNetCore.Razor.Test.Common;
|
||||
using Microsoft.AspNetCore.Razor.Test.Common.Editor;
|
||||
using Microsoft.AspNetCore.Razor.Test.Common.ProjectSystem;
|
||||
using Microsoft.CodeAnalysis.Razor.ProjectSystem;
|
||||
|
@ -30,9 +30,11 @@ public class ProjectSnapshotSynchronizationServiceTest : ProjectSnapshotManagerD
|
|||
{
|
||||
_sessionContext = new TestCollaborationSession(isHost: false);
|
||||
|
||||
var projectManager = new TestProjectSnapshotManager(ProjectEngineFactoryProvider, new TestProjectSnapshotManagerDispatcher());
|
||||
var projectManager = new TestProjectSnapshotManager(
|
||||
ProjectEngineFactoryProvider,
|
||||
Dispatcher);
|
||||
|
||||
var projectManagerAccessorMock = new Mock<IProjectSnapshotManagerAccessor>(MockBehavior.Strict);
|
||||
var projectManagerAccessorMock = new StrictMock<IProjectSnapshotManagerAccessor>();
|
||||
projectManagerAccessorMock
|
||||
.SetupGet(x => x.Instance)
|
||||
.Returns(projectManager);
|
||||
|
@ -43,7 +45,7 @@ public class ProjectSnapshotSynchronizationServiceTest : ProjectSnapshotManagerD
|
|||
TagHelperDescriptorBuilder.Create("TestTagHelper", "TestAssembly").Build()));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[UIFact]
|
||||
public async Task InitializeAsync_RetrievesHostProjectManagerStateAndInitializesGuestManager()
|
||||
{
|
||||
// Arrange
|
||||
|
@ -53,16 +55,19 @@ public class ProjectSnapshotSynchronizationServiceTest : ProjectSnapshotManagerD
|
|||
RazorConfiguration.Default,
|
||||
"project",
|
||||
_projectWorkspaceStateWithTagHelpers);
|
||||
var state = new ProjectSnapshotManagerProxyState(new[] { projectHandle });
|
||||
var hostProjectManagerProxy = Mock.Of<IProjectSnapshotManagerProxy>(
|
||||
proxy => proxy.GetProjectManagerStateAsync(It.IsAny<CancellationToken>()) == Task.FromResult(state), MockBehavior.Strict);
|
||||
var state = new ProjectSnapshotManagerProxyState([projectHandle]);
|
||||
var hostProjectManagerProxyMock = new StrictMock<IProjectSnapshotManagerProxy>();
|
||||
hostProjectManagerProxyMock
|
||||
.Setup(x => x.GetProjectManagerStateAsync(It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(state);
|
||||
|
||||
var synchronizationService = new ProjectSnapshotSynchronizationService(
|
||||
JoinableTaskFactory,
|
||||
_sessionContext,
|
||||
hostProjectManagerProxy,
|
||||
hostProjectManagerProxyMock.Object,
|
||||
_projectManagerAccessor,
|
||||
Dispatcher,
|
||||
ErrorReporter,
|
||||
Dispatcher);
|
||||
JoinableTaskFactory);
|
||||
|
||||
// Act
|
||||
await synchronizationService.InitializeAsync(DisposalToken);
|
||||
|
@ -82,7 +87,7 @@ public class ProjectSnapshotSynchronizationServiceTest : ProjectSnapshotManagerD
|
|||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[UIFact]
|
||||
public async Task UpdateGuestProjectManager_ProjectAdded()
|
||||
{
|
||||
// Arrange
|
||||
|
@ -93,12 +98,12 @@ public class ProjectSnapshotSynchronizationServiceTest : ProjectSnapshotManagerD
|
|||
"project",
|
||||
_projectWorkspaceStateWithTagHelpers);
|
||||
var synchronizationService = new ProjectSnapshotSynchronizationService(
|
||||
JoinableTaskFactory,
|
||||
_sessionContext,
|
||||
Mock.Of<IProjectSnapshotManagerProxy>(MockBehavior.Strict),
|
||||
StrictMock.Of<IProjectSnapshotManagerProxy>(),
|
||||
_projectManagerAccessor,
|
||||
Dispatcher,
|
||||
ErrorReporter,
|
||||
Dispatcher);
|
||||
JoinableTaskFactory);
|
||||
var args = new ProjectChangeEventProxyArgs(older: null, newHandle, ProjectProxyChangeKind.ProjectAdded);
|
||||
|
||||
// Act
|
||||
|
@ -119,7 +124,7 @@ public class ProjectSnapshotSynchronizationServiceTest : ProjectSnapshotManagerD
|
|||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[UIFact]
|
||||
public async Task UpdateGuestProjectManager_ProjectRemoved()
|
||||
{
|
||||
// Arrange
|
||||
|
@ -128,14 +133,14 @@ public class ProjectSnapshotSynchronizationServiceTest : ProjectSnapshotManagerD
|
|||
new Uri("vsls:/path/obj"),
|
||||
RazorConfiguration.Default,
|
||||
"project",
|
||||
projectWorkspaceState: null);
|
||||
ProjectWorkspaceState.Default);
|
||||
var synchronizationService = new ProjectSnapshotSynchronizationService(
|
||||
JoinableTaskFactory,
|
||||
_sessionContext,
|
||||
Mock.Of<IProjectSnapshotManagerProxy>(MockBehavior.Strict),
|
||||
StrictMock.Of<IProjectSnapshotManagerProxy>(),
|
||||
_projectManagerAccessor,
|
||||
Dispatcher,
|
||||
ErrorReporter,
|
||||
Dispatcher);
|
||||
JoinableTaskFactory);
|
||||
var hostProject = new HostProject("/guest/path/project.csproj", "/guest/path/obj", RazorConfiguration.Default, "project");
|
||||
|
||||
await Dispatcher.RunOnDispatcherThreadAsync(
|
||||
|
@ -152,7 +157,7 @@ public class ProjectSnapshotSynchronizationServiceTest : ProjectSnapshotManagerD
|
|||
Assert.Empty(projects);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[UIFact]
|
||||
public async Task UpdateGuestProjectManager_ProjectChanged_ConfigurationChange()
|
||||
{
|
||||
// Arrange
|
||||
|
@ -161,8 +166,8 @@ public class ProjectSnapshotSynchronizationServiceTest : ProjectSnapshotManagerD
|
|||
new Uri("vsls:/path/obj"),
|
||||
RazorConfiguration.Default,
|
||||
"project",
|
||||
projectWorkspaceState: null);
|
||||
var newConfiguration = RazorConfiguration.Create(RazorLanguageVersion.Version_1_0, "Custom-1.0", Enumerable.Empty<RazorExtension>());
|
||||
ProjectWorkspaceState.Default);
|
||||
var newConfiguration = RazorConfiguration.Create(RazorLanguageVersion.Version_1_0, "Custom-1.0", []);
|
||||
var newHandle = new ProjectSnapshotHandleProxy(
|
||||
oldHandle.FilePath,
|
||||
oldHandle.IntermediateOutputPath,
|
||||
|
@ -170,12 +175,12 @@ public class ProjectSnapshotSynchronizationServiceTest : ProjectSnapshotManagerD
|
|||
oldHandle.RootNamespace,
|
||||
oldHandle.ProjectWorkspaceState);
|
||||
var synchronizationService = new ProjectSnapshotSynchronizationService(
|
||||
JoinableTaskFactory,
|
||||
_sessionContext,
|
||||
Mock.Of<IProjectSnapshotManagerProxy>(MockBehavior.Strict),
|
||||
StrictMock.Of<IProjectSnapshotManagerProxy>(),
|
||||
_projectManagerAccessor,
|
||||
Dispatcher,
|
||||
ErrorReporter,
|
||||
Dispatcher);
|
||||
JoinableTaskFactory);
|
||||
var hostProject = new HostProject("/guest/path/project.csproj", "/guest/path/obj", RazorConfiguration.Default, "project");
|
||||
await Dispatcher.RunOnDispatcherThreadAsync(() =>
|
||||
{
|
||||
|
@ -197,7 +202,7 @@ public class ProjectSnapshotSynchronizationServiceTest : ProjectSnapshotManagerD
|
|||
Assert.Empty(await project.GetTagHelpersAsync(CancellationToken.None));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[UIFact]
|
||||
public async Task UpdateGuestProjectManager_ProjectChanged_ProjectWorkspaceStateChange()
|
||||
{
|
||||
// Arrange
|
||||
|
@ -215,12 +220,12 @@ public class ProjectSnapshotSynchronizationServiceTest : ProjectSnapshotManagerD
|
|||
oldHandle.RootNamespace,
|
||||
newProjectWorkspaceState);
|
||||
var synchronizationService = new ProjectSnapshotSynchronizationService(
|
||||
JoinableTaskFactory,
|
||||
_sessionContext,
|
||||
Mock.Of<IProjectSnapshotManagerProxy>(MockBehavior.Strict),
|
||||
StrictMock.Of<IProjectSnapshotManagerProxy>(),
|
||||
_projectManagerAccessor,
|
||||
Dispatcher,
|
||||
ErrorReporter,
|
||||
Dispatcher);
|
||||
JoinableTaskFactory);
|
||||
var hostProject = new HostProject("/guest/path/project.csproj", "/guest/path/obj", RazorConfiguration.Default, "project");
|
||||
await Dispatcher.RunOnDispatcherThreadAsync(() =>
|
||||
{
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT license. See License.txt in the project root for license information.
|
||||
|
||||
using System.Threading;
|
||||
using Microsoft.AspNetCore.Razor.Test.Common;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace Microsoft.VisualStudio.LiveShare.Razor.Guest;
|
||||
|
||||
public class ProxyAccessorTest(ITestOutputHelper testOutput) : ToolingTestBase(testOutput)
|
||||
{
|
||||
[UIFact]
|
||||
public void GetProjectHierarchyProxy_Caches()
|
||||
{
|
||||
// Arrange
|
||||
var projectHierarchyProxy = StrictMock.Of<IProjectHierarchyProxy>();
|
||||
|
||||
var collaborationSessionMock = new StrictMock<CollaborationSession>();
|
||||
collaborationSessionMock
|
||||
.Setup(x => x.GetRemoteServiceAsync<IProjectHierarchyProxy>(typeof(IProjectHierarchyProxy).Name, CancellationToken.None))
|
||||
.ReturnsAsync(projectHierarchyProxy);
|
||||
|
||||
var liveShareSessionAccessorMock = new StrictMock<ILiveShareSessionAccessor>();
|
||||
liveShareSessionAccessorMock
|
||||
.SetupGet(x => x.Session)
|
||||
.Returns(collaborationSessionMock.Object);
|
||||
|
||||
var proxyAccessor = new ProxyAccessor(liveShareSessionAccessorMock.Object, JoinableTaskContext);
|
||||
|
||||
// Act
|
||||
var proxy1 = proxyAccessor.GetProjectHierarchyProxy();
|
||||
var proxy2 = proxyAccessor.GetProjectHierarchyProxy();
|
||||
|
||||
// Assert
|
||||
Assert.Same(proxy1, proxy2);
|
||||
}
|
||||
}
|
|
@ -1,8 +1,6 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT license. See License.txt in the project root for license information.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
@ -13,32 +11,28 @@ using Xunit.Abstractions;
|
|||
|
||||
namespace Microsoft.VisualStudio.LiveShare.Razor.Guest;
|
||||
|
||||
public class RazorGuestInitializationServiceTest : ToolingTestBase
|
||||
public class RazorGuestInitializationServiceTest(ITestOutputHelper testOutput) : ToolingTestBase(testOutput)
|
||||
{
|
||||
private readonly DefaultLiveShareSessionAccessor _liveShareSessionAccessor;
|
||||
|
||||
public RazorGuestInitializationServiceTest(ITestOutputHelper testOutput)
|
||||
: base(testOutput)
|
||||
{
|
||||
_liveShareSessionAccessor = new DefaultLiveShareSessionAccessor();
|
||||
}
|
||||
private readonly LiveShareSessionAccessor _liveShareSessionAccessor = new();
|
||||
|
||||
[Fact]
|
||||
public async Task CreateServiceAsync_StartsViewImportsCopy()
|
||||
{
|
||||
// Arrange
|
||||
var service = new RazorGuestInitializationService(_liveShareSessionAccessor);
|
||||
var session = new Mock<CollaborationSession>(MockBehavior.Strict);
|
||||
session.Setup(s => s.ListRootsAsync(It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(Array.Empty<Uri>())
|
||||
var serviceAccessor = service.GetTestAccessor();
|
||||
var session = new StrictMock<CollaborationSession>();
|
||||
session
|
||||
.Setup(s => s.ListRootsAsync(It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync([])
|
||||
.Verifiable();
|
||||
|
||||
// Act
|
||||
await service.CreateServiceAsync(session.Object, default);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(service._viewImportsCopyTask);
|
||||
await service._viewImportsCopyTask;
|
||||
Assert.NotNull(serviceAccessor.ViewImportsCopyTask);
|
||||
await serviceAccessor.ViewImportsCopyTask;
|
||||
|
||||
session.VerifyAll();
|
||||
}
|
||||
|
@ -48,11 +42,13 @@ public class RazorGuestInitializationServiceTest : ToolingTestBase
|
|||
{
|
||||
// Arrange
|
||||
var service = new RazorGuestInitializationService(_liveShareSessionAccessor);
|
||||
var session = new Mock<CollaborationSession>(MockBehavior.Strict);
|
||||
var serviceAccessor = service.GetTestAccessor();
|
||||
var session = new StrictMock<CollaborationSession>();
|
||||
using var disposedServiceGate = new ManualResetEventSlim();
|
||||
var disposedService = false;
|
||||
IDisposable sessionService = null;
|
||||
session.Setup(s => s.ListRootsAsync(It.IsAny<CancellationToken>()))
|
||||
IDisposable? sessionService = null;
|
||||
session
|
||||
.Setup(s => s.ListRootsAsync(It.IsAny<CancellationToken>()))
|
||||
.Returns<CancellationToken>((cancellationToken) => Task.Run(() =>
|
||||
{
|
||||
cancellationToken.WaitHandle.WaitOne(TimeSpan.FromSeconds(5));
|
||||
|
@ -64,7 +60,7 @@ public class RazorGuestInitializationServiceTest : ToolingTestBase
|
|||
return Array.Empty<Uri>();
|
||||
}))
|
||||
.Verifiable();
|
||||
sessionService = (IDisposable)await service.CreateServiceAsync(session.Object, default);
|
||||
sessionService = (IDisposable)await service.CreateServiceAsync(session.Object, DisposalToken);
|
||||
|
||||
// Act
|
||||
sessionService.Dispose();
|
||||
|
@ -72,8 +68,8 @@ public class RazorGuestInitializationServiceTest : ToolingTestBase
|
|||
disposedServiceGate.Set();
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(service._viewImportsCopyTask);
|
||||
await service._viewImportsCopyTask;
|
||||
Assert.NotNull(serviceAccessor.ViewImportsCopyTask);
|
||||
await serviceAccessor.ViewImportsCopyTask;
|
||||
|
||||
session.VerifyAll();
|
||||
}
|
||||
|
@ -83,10 +79,12 @@ public class RazorGuestInitializationServiceTest : ToolingTestBase
|
|||
{
|
||||
// Arrange
|
||||
var service = new RazorGuestInitializationService(_liveShareSessionAccessor);
|
||||
var session = new Mock<CollaborationSession>(MockBehavior.Strict);
|
||||
var serviceAccessor = service.GetTestAccessor();
|
||||
var session = new StrictMock<CollaborationSession>();
|
||||
using var cts = new CancellationTokenSource();
|
||||
IDisposable sessionService = null;
|
||||
session.Setup(s => s.ListRootsAsync(It.IsAny<CancellationToken>()))
|
||||
IDisposable? sessionService = null;
|
||||
session
|
||||
.Setup(s => s.ListRootsAsync(It.IsAny<CancellationToken>()))
|
||||
.Returns<CancellationToken>((cancellationToken) => Task.Run(() =>
|
||||
{
|
||||
cancellationToken.WaitHandle.WaitOne(TimeSpan.FromSeconds(3));
|
||||
|
@ -101,8 +99,8 @@ public class RazorGuestInitializationServiceTest : ToolingTestBase
|
|||
cts.Cancel();
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(service._viewImportsCopyTask);
|
||||
await service._viewImportsCopyTask;
|
||||
Assert.NotNull(serviceAccessor.ViewImportsCopyTask);
|
||||
await serviceAccessor.ViewImportsCopyTask;
|
||||
|
||||
session.VerifyAll();
|
||||
}
|
||||
|
@ -112,10 +110,12 @@ public class RazorGuestInitializationServiceTest : ToolingTestBase
|
|||
{
|
||||
// Arrange
|
||||
var service = new RazorGuestInitializationService(_liveShareSessionAccessor);
|
||||
var session = new Mock<CollaborationSession>(MockBehavior.Strict);
|
||||
var serviceAcessor = service.GetTestAccessor();
|
||||
var session = new StrictMock<CollaborationSession>();
|
||||
using var cts = new CancellationTokenSource();
|
||||
IDisposable sessionService = null;
|
||||
session.Setup(s => s.ListRootsAsync(It.IsAny<CancellationToken>()))
|
||||
IDisposable? sessionService = null;
|
||||
session
|
||||
.Setup(s => s.ListRootsAsync(It.IsAny<CancellationToken>()))
|
||||
.Returns<CancellationToken>((cancellationToken) => Task.Run(() =>
|
||||
{
|
||||
cancellationToken.WaitHandle.WaitOne(TimeSpan.FromSeconds(3));
|
||||
|
@ -131,8 +131,8 @@ public class RazorGuestInitializationServiceTest : ToolingTestBase
|
|||
cts.Cancel();
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(service._viewImportsCopyTask);
|
||||
await service._viewImportsCopyTask;
|
||||
Assert.NotNull(serviceAcessor.ViewImportsCopyTask);
|
||||
await serviceAcessor.ViewImportsCopyTask;
|
||||
|
||||
session.VerifyAll();
|
||||
}
|
||||
|
|
|
@ -1,229 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT license. See License.txt in the project root for license information.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Collections.Immutable;
|
||||
using System.Threading;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.AspNetCore.Razor.ProjectEngineHost;
|
||||
using Microsoft.AspNetCore.Razor.ProjectSystem;
|
||||
using Microsoft.AspNetCore.Razor.Test.Common.Editor;
|
||||
using Microsoft.CodeAnalysis.Razor.ProjectSystem;
|
||||
using Microsoft.VisualStudio.LiveShare.Razor.Test;
|
||||
using Microsoft.VisualStudio.Threading;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace Microsoft.VisualStudio.LiveShare.Razor.Host;
|
||||
|
||||
public class DefaultProjectSnapshotManagerProxyTest : ProjectSnapshotManagerDispatcherTestBase
|
||||
{
|
||||
private readonly IProjectSnapshot _projectSnapshot1;
|
||||
private readonly IProjectSnapshot _projectSnapshot2;
|
||||
|
||||
public DefaultProjectSnapshotManagerProxyTest(ITestOutputHelper testOutput)
|
||||
: base(testOutput)
|
||||
{
|
||||
var projectEngineFactoryProvider = Mock.Of<IProjectEngineFactoryProvider>(MockBehavior.Strict);
|
||||
|
||||
var projectWorkspaceState1 = ProjectWorkspaceState.Create(ImmutableArray.Create(
|
||||
TagHelperDescriptorBuilder.Create("test1", "TestAssembly1").Build()));
|
||||
|
||||
_projectSnapshot1 = new ProjectSnapshot(
|
||||
ProjectState.Create(
|
||||
projectEngineFactoryProvider,
|
||||
new HostProject("/host/path/to/project1.csproj", "/host/path/to/obj", RazorConfiguration.Default, "project1"),
|
||||
projectWorkspaceState1));
|
||||
|
||||
var projectWorkspaceState2 = ProjectWorkspaceState.Create(ImmutableArray.Create(
|
||||
TagHelperDescriptorBuilder.Create("test2", "TestAssembly2").Build()));
|
||||
|
||||
_projectSnapshot2 = new ProjectSnapshot(
|
||||
ProjectState.Create(
|
||||
projectEngineFactoryProvider,
|
||||
new HostProject("/host/path/to/project2.csproj", "/host/path/to/obj", RazorConfiguration.Default, "project2"),
|
||||
projectWorkspaceState2));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CalculateUpdatedStateAsync_ReturnsStateForAllProjects()
|
||||
{
|
||||
// Arrange
|
||||
var projectSnapshotManager = new TestProjectSnapshotManager(_projectSnapshot1, _projectSnapshot2);
|
||||
using var proxy = new DefaultProjectSnapshotManagerProxy(
|
||||
new TestCollaborationSession(true),
|
||||
Dispatcher,
|
||||
projectSnapshotManager,
|
||||
JoinableTaskFactory);
|
||||
|
||||
// Act
|
||||
var state = await JoinableTaskFactory.RunAsync(() => proxy.CalculateUpdatedStateAsync(projectSnapshotManager.GetProjects()));
|
||||
|
||||
// Assert
|
||||
var project1TagHelpers = await _projectSnapshot1.GetTagHelpersAsync(CancellationToken.None);
|
||||
var project2TagHelpers = await _projectSnapshot2.GetTagHelpersAsync(CancellationToken.None);
|
||||
|
||||
Assert.Collection(
|
||||
state.ProjectHandles,
|
||||
handle =>
|
||||
{
|
||||
Assert.Equal("vsls:/path/to/project1.csproj", handle.FilePath.ToString());
|
||||
Assert.Equal<TagHelperDescriptor>(project1TagHelpers, handle.ProjectWorkspaceState.TagHelpers);
|
||||
},
|
||||
handle =>
|
||||
{
|
||||
Assert.Equal("vsls:/path/to/project2.csproj", handle.FilePath.ToString());
|
||||
Assert.Equal<TagHelperDescriptor>(project2TagHelpers, handle.ProjectWorkspaceState.TagHelpers);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Changed_TriggersOnSnapshotManagerChanged()
|
||||
{
|
||||
// Arrange
|
||||
var projectSnapshotManager = new TestProjectSnapshotManager(_projectSnapshot1);
|
||||
using var proxy = new DefaultProjectSnapshotManagerProxy(
|
||||
new TestCollaborationSession(true),
|
||||
Dispatcher,
|
||||
projectSnapshotManager,
|
||||
JoinableTaskFactory);
|
||||
var changedArgs = new ProjectChangeEventArgs(_projectSnapshot1, _projectSnapshot1, ProjectChangeKind.ProjectChanged);
|
||||
var called = false;
|
||||
proxy.Changed += (sender, args) =>
|
||||
{
|
||||
called = true;
|
||||
Assert.Equal($"vsls:/path/to/project1.csproj", args.ProjectFilePath.ToString());
|
||||
Assert.Equal(ProjectProxyChangeKind.ProjectChanged, args.Kind);
|
||||
Assert.Equal("vsls:/path/to/project1.csproj", args.Newer.FilePath.ToString());
|
||||
};
|
||||
|
||||
// Act
|
||||
projectSnapshotManager.TriggerChanged(changedArgs);
|
||||
await proxy._processingChangedEventTestTask.JoinAsync();
|
||||
|
||||
// Assert
|
||||
Assert.True(called);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Changed_NoopsIfProxyDisposed()
|
||||
{
|
||||
// Arrange
|
||||
var projectSnapshotManager = new TestProjectSnapshotManager(_projectSnapshot1);
|
||||
var proxy = new DefaultProjectSnapshotManagerProxy(
|
||||
new TestCollaborationSession(true),
|
||||
Dispatcher,
|
||||
projectSnapshotManager,
|
||||
JoinableTaskFactory);
|
||||
var changedArgs = new ProjectChangeEventArgs(_projectSnapshot1, _projectSnapshot1, ProjectChangeKind.ProjectChanged);
|
||||
proxy.Changed += (sender, args) => throw new InvalidOperationException("Should not have been called.");
|
||||
proxy.Dispose();
|
||||
|
||||
// Act
|
||||
projectSnapshotManager.TriggerChanged(changedArgs);
|
||||
|
||||
// Assert
|
||||
Assert.Null(proxy._processingChangedEventTestTask);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetLatestProjectsAsync_ReturnsSnapshotManagerProjects()
|
||||
{
|
||||
// Arrange
|
||||
var projectSnapshotManager = new TestProjectSnapshotManager(_projectSnapshot1);
|
||||
using var proxy = new DefaultProjectSnapshotManagerProxy(
|
||||
new TestCollaborationSession(true),
|
||||
Dispatcher,
|
||||
projectSnapshotManager,
|
||||
JoinableTaskFactory);
|
||||
|
||||
// Act
|
||||
var projects = await proxy.GetLatestProjectsAsync();
|
||||
|
||||
// Assert
|
||||
var project = Assert.Single(projects);
|
||||
Assert.Same(_projectSnapshot1, project);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetStateAsync_ReturnsProjectState()
|
||||
{
|
||||
// Arrange
|
||||
var projectSnapshotManager = new TestProjectSnapshotManager(_projectSnapshot1, _projectSnapshot2);
|
||||
using var proxy = new DefaultProjectSnapshotManagerProxy(
|
||||
new TestCollaborationSession(true),
|
||||
Dispatcher,
|
||||
projectSnapshotManager,
|
||||
JoinableTaskFactory);
|
||||
|
||||
// Act
|
||||
var state = await JoinableTaskFactory.RunAsync(() => proxy.GetProjectManagerStateAsync(DisposalToken));
|
||||
|
||||
// Assert
|
||||
var project1TagHelpers = await _projectSnapshot1.GetTagHelpersAsync(CancellationToken.None);
|
||||
var project2TagHelpers = await _projectSnapshot2.GetTagHelpersAsync(CancellationToken.None);
|
||||
|
||||
Assert.Collection(
|
||||
state.ProjectHandles,
|
||||
handle =>
|
||||
{
|
||||
Assert.Equal("vsls:/path/to/project1.csproj", handle.FilePath.ToString());
|
||||
Assert.Equal<TagHelperDescriptor>(project1TagHelpers, handle.ProjectWorkspaceState.TagHelpers);
|
||||
},
|
||||
handle =>
|
||||
{
|
||||
Assert.Equal("vsls:/path/to/project2.csproj", handle.FilePath.ToString());
|
||||
Assert.Equal<TagHelperDescriptor>(project2TagHelpers, handle.ProjectWorkspaceState.TagHelpers);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetStateAsync_CachesState()
|
||||
{
|
||||
// Arrange
|
||||
var projectSnapshotManager = new TestProjectSnapshotManager(_projectSnapshot1);
|
||||
using var proxy = new DefaultProjectSnapshotManagerProxy(
|
||||
new TestCollaborationSession(true),
|
||||
Dispatcher,
|
||||
projectSnapshotManager,
|
||||
JoinableTaskFactory);
|
||||
|
||||
// Act
|
||||
var state1 = await JoinableTaskFactory.RunAsync(() => proxy.GetProjectManagerStateAsync(DisposalToken));
|
||||
var state2 = await JoinableTaskFactory.RunAsync(() => proxy.GetProjectManagerStateAsync(DisposalToken));
|
||||
|
||||
// Assert
|
||||
Assert.Same(state1, state2);
|
||||
}
|
||||
|
||||
private class TestProjectSnapshotManager(params IProjectSnapshot[] projects) : IProjectSnapshotManager
|
||||
{
|
||||
private ImmutableArray<IProjectSnapshot> _projects = projects.ToImmutableArray();
|
||||
|
||||
public ImmutableArray<IProjectSnapshot> GetProjects() => _projects;
|
||||
|
||||
public event EventHandler<ProjectChangeEventArgs> Changed;
|
||||
|
||||
public void TriggerChanged(ProjectChangeEventArgs args)
|
||||
{
|
||||
Changed?.Invoke(this, args);
|
||||
}
|
||||
|
||||
public IProjectSnapshot GetLoadedProject(ProjectKey projectKey)
|
||||
=> throw new NotImplementedException();
|
||||
|
||||
public ImmutableArray<ProjectKey> GetAllProjectKeys(string projectFileName)
|
||||
=> throw new NotImplementedException();
|
||||
|
||||
public bool IsDocumentOpen(string documentFilePath)
|
||||
=> throw new NotImplementedException();
|
||||
|
||||
public bool TryGetLoadedProject(ProjectKey projectKey, [NotNullWhen(true)] out IProjectSnapshot project)
|
||||
=> throw new NotImplementedException();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,231 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT license. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Razor;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.AspNetCore.Razor.ProjectEngineHost;
|
||||
using Microsoft.AspNetCore.Razor.ProjectSystem;
|
||||
using Microsoft.AspNetCore.Razor.Test.Common;
|
||||
using Microsoft.AspNetCore.Razor.Test.Common.Editor;
|
||||
using Microsoft.CodeAnalysis.Razor.ProjectSystem;
|
||||
using Microsoft.VisualStudio.LiveShare.Razor.Test;
|
||||
using Microsoft.VisualStudio.Threading;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace Microsoft.VisualStudio.LiveShare.Razor.Host;
|
||||
|
||||
public class ProjectSnapshotManagerProxyTest : ProjectSnapshotManagerDispatcherTestBase
|
||||
{
|
||||
private readonly IProjectSnapshot _projectSnapshot1;
|
||||
private readonly IProjectSnapshot _projectSnapshot2;
|
||||
|
||||
public ProjectSnapshotManagerProxyTest(ITestOutputHelper testOutput)
|
||||
: base(testOutput)
|
||||
{
|
||||
var projectEngineFactoryProvider = StrictMock.Of<IProjectEngineFactoryProvider>();
|
||||
|
||||
var projectWorkspaceState1 = ProjectWorkspaceState.Create(ImmutableArray.Create(
|
||||
TagHelperDescriptorBuilder.Create("test1", "TestAssembly1").Build()));
|
||||
|
||||
_projectSnapshot1 = new ProjectSnapshot(
|
||||
ProjectState.Create(
|
||||
projectEngineFactoryProvider,
|
||||
new HostProject("/host/path/to/project1.csproj", "/host/path/to/obj", RazorConfiguration.Default, "project1"),
|
||||
projectWorkspaceState1));
|
||||
|
||||
var projectWorkspaceState2 = ProjectWorkspaceState.Create(ImmutableArray.Create(
|
||||
TagHelperDescriptorBuilder.Create("test2", "TestAssembly2").Build()));
|
||||
|
||||
_projectSnapshot2 = new ProjectSnapshot(
|
||||
ProjectState.Create(
|
||||
projectEngineFactoryProvider,
|
||||
new HostProject("/host/path/to/project2.csproj", "/host/path/to/obj", RazorConfiguration.Default, "project2"),
|
||||
projectWorkspaceState2));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CalculateUpdatedStateAsync_ReturnsStateForAllProjects()
|
||||
{
|
||||
// Arrange
|
||||
var projectSnapshotManager = new TestProjectSnapshotManager(_projectSnapshot1, _projectSnapshot2);
|
||||
using var proxy = new ProjectSnapshotManagerProxy(
|
||||
new TestCollaborationSession(true),
|
||||
projectSnapshotManager,
|
||||
Dispatcher,
|
||||
JoinableTaskFactory);
|
||||
|
||||
// Act
|
||||
var state = await JoinableTaskFactory.RunAsync(() => proxy.CalculateUpdatedStateAsync(projectSnapshotManager.GetProjects()));
|
||||
|
||||
// Assert
|
||||
var project1TagHelpers = await _projectSnapshot1.GetTagHelpersAsync(CancellationToken.None);
|
||||
var project2TagHelpers = await _projectSnapshot2.GetTagHelpersAsync(CancellationToken.None);
|
||||
|
||||
Assert.Collection(
|
||||
state.ProjectHandles,
|
||||
handle =>
|
||||
{
|
||||
Assert.Equal("vsls:/path/to/project1.csproj", handle.FilePath.ToString());
|
||||
Assert.Equal<TagHelperDescriptor>(project1TagHelpers, handle.ProjectWorkspaceState.TagHelpers);
|
||||
},
|
||||
handle =>
|
||||
{
|
||||
Assert.Equal("vsls:/path/to/project2.csproj", handle.FilePath.ToString());
|
||||
Assert.Equal<TagHelperDescriptor>(project2TagHelpers, handle.ProjectWorkspaceState.TagHelpers);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Changed_TriggersOnSnapshotManagerChanged()
|
||||
{
|
||||
// Arrange
|
||||
var projectSnapshotManager = new TestProjectSnapshotManager(_projectSnapshot1);
|
||||
using var proxy = new ProjectSnapshotManagerProxy(
|
||||
new TestCollaborationSession(true),
|
||||
projectSnapshotManager,
|
||||
Dispatcher,
|
||||
JoinableTaskFactory);
|
||||
var proxyAccessor = proxy.GetTestAccessor();
|
||||
var changedArgs = new ProjectChangeEventArgs(_projectSnapshot1, _projectSnapshot1, ProjectChangeKind.ProjectChanged);
|
||||
var called = false;
|
||||
proxy.Changed += (sender, args) =>
|
||||
{
|
||||
called = true;
|
||||
Assert.Equal($"vsls:/path/to/project1.csproj", args.ProjectFilePath.ToString());
|
||||
Assert.Equal(ProjectProxyChangeKind.ProjectChanged, args.Kind);
|
||||
Assert.NotNull(args.Newer);
|
||||
Assert.Equal("vsls:/path/to/project1.csproj", args.Newer.FilePath.ToString());
|
||||
};
|
||||
|
||||
// Act
|
||||
projectSnapshotManager.TriggerChanged(changedArgs);
|
||||
await proxyAccessor.ProcessingChangedEventTestTask.AssumeNotNull().JoinAsync();
|
||||
|
||||
// Assert
|
||||
Assert.True(called);
|
||||
}
|
||||
|
||||
[UIFact]
|
||||
public void Changed_NoopsIfProxyDisposed()
|
||||
{
|
||||
// Arrange
|
||||
var projectSnapshotManager = new TestProjectSnapshotManager(_projectSnapshot1);
|
||||
var proxy = new ProjectSnapshotManagerProxy(
|
||||
new TestCollaborationSession(true),
|
||||
projectSnapshotManager,
|
||||
Dispatcher,
|
||||
JoinableTaskFactory);
|
||||
var proxyAccessor = proxy.GetTestAccessor();
|
||||
var changedArgs = new ProjectChangeEventArgs(_projectSnapshot1, _projectSnapshot1, ProjectChangeKind.ProjectChanged);
|
||||
proxy.Changed += (sender, args) => throw new InvalidOperationException("Should not have been called.");
|
||||
proxy.Dispose();
|
||||
|
||||
// Act
|
||||
projectSnapshotManager.TriggerChanged(changedArgs);
|
||||
|
||||
// Assert
|
||||
Assert.Null(proxyAccessor.ProcessingChangedEventTestTask);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetLatestProjectsAsync_ReturnsSnapshotManagerProjects()
|
||||
{
|
||||
// Arrange
|
||||
var projectSnapshotManager = new TestProjectSnapshotManager(_projectSnapshot1);
|
||||
using var proxy = new ProjectSnapshotManagerProxy(
|
||||
new TestCollaborationSession(true),
|
||||
projectSnapshotManager,
|
||||
Dispatcher,
|
||||
JoinableTaskFactory);
|
||||
|
||||
// Act
|
||||
var projects = await proxy.GetLatestProjectsAsync();
|
||||
|
||||
// Assert
|
||||
var project = Assert.Single(projects);
|
||||
Assert.Same(_projectSnapshot1, project);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetStateAsync_ReturnsProjectState()
|
||||
{
|
||||
// Arrange
|
||||
var projectSnapshotManager = new TestProjectSnapshotManager(_projectSnapshot1, _projectSnapshot2);
|
||||
using var proxy = new ProjectSnapshotManagerProxy(
|
||||
new TestCollaborationSession(true),
|
||||
projectSnapshotManager,
|
||||
Dispatcher,
|
||||
JoinableTaskFactory);
|
||||
|
||||
// Act
|
||||
var state = await JoinableTaskFactory.RunAsync(() => proxy.GetProjectManagerStateAsync(DisposalToken));
|
||||
|
||||
// Assert
|
||||
var project1TagHelpers = await _projectSnapshot1.GetTagHelpersAsync(CancellationToken.None);
|
||||
var project2TagHelpers = await _projectSnapshot2.GetTagHelpersAsync(CancellationToken.None);
|
||||
|
||||
Assert.Collection(
|
||||
state.ProjectHandles,
|
||||
handle =>
|
||||
{
|
||||
Assert.Equal("vsls:/path/to/project1.csproj", handle.FilePath.ToString());
|
||||
Assert.Equal<TagHelperDescriptor>(project1TagHelpers, handle.ProjectWorkspaceState.TagHelpers);
|
||||
},
|
||||
handle =>
|
||||
{
|
||||
Assert.Equal("vsls:/path/to/project2.csproj", handle.FilePath.ToString());
|
||||
Assert.Equal<TagHelperDescriptor>(project2TagHelpers, handle.ProjectWorkspaceState.TagHelpers);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetStateAsync_CachesState()
|
||||
{
|
||||
// Arrange
|
||||
var projectSnapshotManager = new TestProjectSnapshotManager(_projectSnapshot1);
|
||||
using var proxy = new ProjectSnapshotManagerProxy(
|
||||
new TestCollaborationSession(true),
|
||||
projectSnapshotManager,
|
||||
Dispatcher,
|
||||
JoinableTaskFactory);
|
||||
|
||||
// Act
|
||||
var state1 = await JoinableTaskFactory.RunAsync(() => proxy.GetProjectManagerStateAsync(DisposalToken));
|
||||
var state2 = await JoinableTaskFactory.RunAsync(() => proxy.GetProjectManagerStateAsync(DisposalToken));
|
||||
|
||||
// Assert
|
||||
Assert.Same(state1, state2);
|
||||
}
|
||||
|
||||
private sealed class TestProjectSnapshotManager(params IProjectSnapshot[] projects) : IProjectSnapshotManager
|
||||
{
|
||||
private readonly ImmutableArray<IProjectSnapshot> _projects = projects.ToImmutableArray();
|
||||
|
||||
public ImmutableArray<IProjectSnapshot> GetProjects() => _projects;
|
||||
|
||||
public event EventHandler<ProjectChangeEventArgs>? Changed;
|
||||
|
||||
public void TriggerChanged(ProjectChangeEventArgs args)
|
||||
{
|
||||
Changed?.Invoke(this, args);
|
||||
}
|
||||
|
||||
public IProjectSnapshot GetLoadedProject(ProjectKey projectKey)
|
||||
=> throw new NotImplementedException();
|
||||
|
||||
public ImmutableArray<ProjectKey> GetAllProjectKeys(string projectFileName)
|
||||
=> throw new NotImplementedException();
|
||||
|
||||
public bool IsDocumentOpen(string documentFilePath)
|
||||
=> throw new NotImplementedException();
|
||||
|
||||
public bool TryGetLoadedProject(ProjectKey projectKey, [NotNullWhen(true)] out IProjectSnapshot project)
|
||||
=> throw new NotImplementedException();
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче