Add an example

This commit is contained in:
Ryan Nowak 2017-08-30 16:59:30 -07:00
parent 9dfe2a0a81
commit ae925049bb
5 changed files with 168 additions and 122 deletions

View File

@ -21,15 +21,22 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
[Export(typeof(VisualStudioDocumentTrackerFactory))] [Export(typeof(VisualStudioDocumentTrackerFactory))]
internal class DefaultVisualStudioDocumentTrackerFactory : VisualStudioDocumentTrackerFactory, IWpfTextViewConnectionListener internal class DefaultVisualStudioDocumentTrackerFactory : VisualStudioDocumentTrackerFactory, IWpfTextViewConnectionListener
{ {
private readonly ForegroundDispatcher _foregroundDispatcher;
private readonly ProjectSnapshotManager _projectManager; private readonly ProjectSnapshotManager _projectManager;
private readonly TextBufferProjectService _projectService; private readonly TextBufferProjectService _projectService;
private readonly Workspace _workspace; private readonly Workspace _workspace;
[ImportingConstructor] [ImportingConstructor]
public DefaultVisualStudioDocumentTrackerFactory( public DefaultVisualStudioDocumentTrackerFactory(
ForegroundDispatcher foregroundDispatcher,
TextBufferProjectService projectService, TextBufferProjectService projectService,
[Import(typeof(VisualStudioWorkspace))] Workspace workspace) [Import(typeof(VisualStudioWorkspace))] Workspace workspace)
{ {
if (foregroundDispatcher == null)
{
throw new ArgumentNullException(nameof(foregroundDispatcher));
}
if (projectService == null) if (projectService == null)
{ {
throw new ArgumentNullException(nameof(projectService)); throw new ArgumentNullException(nameof(projectService));
@ -39,7 +46,8 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
{ {
throw new ArgumentNullException(nameof(workspace)); throw new ArgumentNullException(nameof(workspace));
} }
_foregroundDispatcher = foregroundDispatcher;
_projectService = projectService; _projectService = projectService;
_workspace = workspace; _workspace = workspace;
@ -48,10 +56,16 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
// This is only for testing. We want to avoid using the actual Roslyn GetService methods in unit tests. // This is only for testing. We want to avoid using the actual Roslyn GetService methods in unit tests.
internal DefaultVisualStudioDocumentTrackerFactory( internal DefaultVisualStudioDocumentTrackerFactory(
ForegroundDispatcher foregroundDispatcher,
ProjectSnapshotManager projectManager, ProjectSnapshotManager projectManager,
TextBufferProjectService projectService, TextBufferProjectService projectService,
[Import(typeof(VisualStudioWorkspace))] Workspace workspace) [Import(typeof(VisualStudioWorkspace))] Workspace workspace)
{ {
if (foregroundDispatcher == null)
{
throw new ArgumentNullException(nameof(foregroundDispatcher));
}
if (projectManager == null) if (projectManager == null)
{ {
throw new ArgumentNullException(nameof(projectManager)); throw new ArgumentNullException(nameof(projectManager));
@ -67,6 +81,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
throw new ArgumentNullException(nameof(workspace)); throw new ArgumentNullException(nameof(workspace));
} }
_foregroundDispatcher = foregroundDispatcher;
_projectManager = projectManager; _projectManager = projectManager;
_projectService = projectService; _projectService = projectService;
_workspace = workspace; _workspace = workspace;
@ -81,6 +96,8 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
throw new ArgumentNullException(nameof(textView)); throw new ArgumentNullException(nameof(textView));
} }
_foregroundDispatcher.AssertForegroundThread();
// While it's definitely possible to have multiple Razor text buffers attached to the same text view, there's // While it's definitely possible to have multiple Razor text buffers attached to the same text view, there's
// no real scenario for it. This method always returns the tracker for the first Razor text buffer, but the // no real scenario for it. This method always returns the tracker for the first Razor text buffer, but the
// other functionality for this class will maintain state correctly for the other buffers. // other functionality for this class will maintain state correctly for the other buffers.
@ -113,7 +130,9 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
{ {
throw new ArgumentNullException(nameof(subjectBuffers)); throw new ArgumentNullException(nameof(subjectBuffers));
} }
_foregroundDispatcher.AssertForegroundThread();
for (var i = 0; i < subjectBuffers.Count; i++) for (var i = 0; i < subjectBuffers.Count; i++)
{ {
var textBuffer = subjectBuffers[i]; var textBuffer = subjectBuffers[i];
@ -148,6 +167,8 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
throw new ArgumentNullException(nameof(subjectBuffers)); throw new ArgumentNullException(nameof(subjectBuffers));
} }
_foregroundDispatcher.AssertForegroundThread();
// This means a Razor buffer has be detached from this ITextView or the ITextView is closing. Since we keep a // This means a Razor buffer has be detached from this ITextView or the ITextView is closing. Since we keep a
// list of all of the open text views for each text buffer, we need to update the tracker. // list of all of the open text views for each text buffer, we need to update the tracker.
// //

View File

@ -16,7 +16,7 @@ using Xunit;
namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
{ {
public class DefaultVisualStudioDocumentTrackerFactoryTest public class DefaultVisualStudioDocumentTrackerFactoryTest : ForegroundDispatcherTestBase
{ {
private ProjectSnapshotManager ProjectManager { get; } = Mock.Of<ProjectSnapshotManager>( private ProjectSnapshotManager ProjectManager { get; } = Mock.Of<ProjectSnapshotManager>(
p => p.FindProject(It.IsAny<string>()) == Mock.Of<ProjectSnapshot>() && p => p.FindProject(It.IsAny<string>()) == Mock.Of<ProjectSnapshot>() &&
@ -32,11 +32,11 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
private IContentType NonRazorContentType { get; } = Mock.Of<IContentType>(c => c.IsOfType(It.IsAny<string>()) == false); private IContentType NonRazorContentType { get; } = Mock.Of<IContentType>(c => c.IsOfType(It.IsAny<string>()) == false);
[Fact] [ForegroundFact]
public void SubjectBuffersConnected_ForNonRazorTextBuffer_DoesNothing() public void SubjectBuffersConnected_ForNonRazorTextBuffer_DoesNothing()
{ {
// Arrange // Arrange
var factory = new DefaultVisualStudioDocumentTrackerFactory(ProjectManager, ProjectService, Workspace); var factory = new DefaultVisualStudioDocumentTrackerFactory(Dispatcher, ProjectManager, ProjectService, Workspace);
var textView = Mock.Of<IWpfTextView>(); var textView = Mock.Of<IWpfTextView>();
@ -52,11 +52,11 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
Assert.False(buffers[0].Properties.ContainsProperty(typeof(VisualStudioDocumentTracker))); Assert.False(buffers[0].Properties.ContainsProperty(typeof(VisualStudioDocumentTracker)));
} }
[Fact] [ForegroundFact]
public void SubjectBuffersConnected_ForRazorTextBufferWithoutTracker_CreatesTrackerAndTracksTextView() public void SubjectBuffersConnected_ForRazorTextBufferWithoutTracker_CreatesTrackerAndTracksTextView()
{ {
// Arrange // Arrange
var factory = new DefaultVisualStudioDocumentTrackerFactory(ProjectManager, ProjectService, Workspace); var factory = new DefaultVisualStudioDocumentTrackerFactory(Dispatcher, ProjectManager, ProjectService, Workspace);
var textView = Mock.Of<IWpfTextView>(); var textView = Mock.Of<IWpfTextView>();
@ -74,11 +74,11 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
Assert.Equal(buffers[0], tracker.TextBuffer); Assert.Equal(buffers[0], tracker.TextBuffer);
} }
[Fact] [ForegroundFact]
public void SubjectBuffersConnected_ForRazorTextBufferWithoutTracker_CreatesTrackerAndTracksTextView_ForMultipleBuffers() public void SubjectBuffersConnected_ForRazorTextBufferWithoutTracker_CreatesTrackerAndTracksTextView_ForMultipleBuffers()
{ {
// Arrange // Arrange
var factory = new DefaultVisualStudioDocumentTrackerFactory(ProjectManager, ProjectService, Workspace); var factory = new DefaultVisualStudioDocumentTrackerFactory(Dispatcher, ProjectManager, ProjectService, Workspace);
var textView = Mock.Of<IWpfTextView>(); var textView = Mock.Of<IWpfTextView>();
@ -104,11 +104,11 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
Assert.Equal(buffers[2], tracker.TextBuffer); Assert.Equal(buffers[2], tracker.TextBuffer);
} }
[Fact] [ForegroundFact]
public void SubjectBuffersConnected_ForRazorTextBufferWithTracker_DoesNotAddDuplicateTextViewEntry() public void SubjectBuffersConnected_ForRazorTextBufferWithTracker_DoesNotAddDuplicateTextViewEntry()
{ {
// Arrange // Arrange
var factory = new DefaultVisualStudioDocumentTrackerFactory(ProjectManager, ProjectService, Workspace); var factory = new DefaultVisualStudioDocumentTrackerFactory(Dispatcher, ProjectManager, ProjectService, Workspace);
var textView = Mock.Of<IWpfTextView>(); var textView = Mock.Of<IWpfTextView>();
@ -130,11 +130,11 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
Assert.Collection(tracker.TextViews, v => Assert.Same(v, textView)); Assert.Collection(tracker.TextViews, v => Assert.Same(v, textView));
} }
[Fact] [ForegroundFact]
public void SubjectBuffersConnected_ForRazorTextBufferWithTracker_AddsEntryForADifferentTextView() public void SubjectBuffersConnected_ForRazorTextBufferWithTracker_AddsEntryForADifferentTextView()
{ {
// Arrange // Arrange
var factory = new DefaultVisualStudioDocumentTrackerFactory(ProjectManager, ProjectService, Workspace); var factory = new DefaultVisualStudioDocumentTrackerFactory(Dispatcher, ProjectManager, ProjectService, Workspace);
var textView1 = Mock.Of<IWpfTextView>(); var textView1 = Mock.Of<IWpfTextView>();
var textView2 = Mock.Of<IWpfTextView>(); var textView2 = Mock.Of<IWpfTextView>();
@ -157,11 +157,11 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
Assert.Collection(tracker.TextViews, v => Assert.Same(v, textView1), v => Assert.Same(v, textView2)); Assert.Collection(tracker.TextViews, v => Assert.Same(v, textView1), v => Assert.Same(v, textView2));
} }
[Fact] [ForegroundFact]
public void SubjectBuffersDisconnected_ForAnyTextBufferWithTracker_RemovesTextView() public void SubjectBuffersDisconnected_ForAnyTextBufferWithTracker_RemovesTextView()
{ {
// Arrange // Arrange
var factory = new DefaultVisualStudioDocumentTrackerFactory(ProjectManager, ProjectService, Workspace); var factory = new DefaultVisualStudioDocumentTrackerFactory(Dispatcher, ProjectManager, ProjectService, Workspace);
var textView1 = Mock.Of<IWpfTextView>(); var textView1 = Mock.Of<IWpfTextView>();
var textView2 = Mock.Of<IWpfTextView>(); var textView2 = Mock.Of<IWpfTextView>();
@ -194,11 +194,11 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
Assert.Collection(tracker.TextViews, v => Assert.Same(v, textView1)); Assert.Collection(tracker.TextViews, v => Assert.Same(v, textView1));
} }
[Fact] [ForegroundFact]
public void SubjectBuffersDisconnected_ForAnyTextBufferWithoutTracker_DoesNothing() public void SubjectBuffersDisconnected_ForAnyTextBufferWithoutTracker_DoesNothing()
{ {
// Arrange // Arrange
var factory = new DefaultVisualStudioDocumentTrackerFactory(ProjectManager, ProjectService, Workspace); var factory = new DefaultVisualStudioDocumentTrackerFactory(Dispatcher, ProjectManager, ProjectService, Workspace);
var textView = Mock.Of<IWpfTextView>(); var textView = Mock.Of<IWpfTextView>();
@ -214,11 +214,11 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
Assert.False(buffers[0].Properties.ContainsProperty(typeof(VisualStudioDocumentTracker))); Assert.False(buffers[0].Properties.ContainsProperty(typeof(VisualStudioDocumentTracker)));
} }
[Fact] [ForegroundFact]
public void GetTracker_ForRazorTextBufferWithTracker_ReturnsTheFirstTracker() public void GetTracker_ForRazorTextBufferWithTracker_ReturnsTheFirstTracker()
{ {
// Arrange // Arrange
var factory = new DefaultVisualStudioDocumentTrackerFactory(ProjectManager, ProjectService, Workspace); var factory = new DefaultVisualStudioDocumentTrackerFactory(Dispatcher, ProjectManager, ProjectService, Workspace);
var buffers = new Collection<ITextBuffer>() var buffers = new Collection<ITextBuffer>()
{ {
@ -241,11 +241,11 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
Assert.Same(tracker, result); Assert.Same(tracker, result);
} }
[Fact] [ForegroundFact]
public void GetTracker_WithoutRazorBuffer_ReturnsNull() public void GetTracker_WithoutRazorBuffer_ReturnsNull()
{ {
// Arrange // Arrange
var factory = new DefaultVisualStudioDocumentTrackerFactory(ProjectManager, ProjectService, Workspace); var factory = new DefaultVisualStudioDocumentTrackerFactory(Dispatcher, ProjectManager, ProjectService, Workspace);
var buffers = new Collection<ITextBuffer>(); var buffers = new Collection<ITextBuffer>();

View File

@ -8,7 +8,7 @@ namespace Xunit
{ {
// Similar to WpfFactAttribute https://github.com/xunit/samples.xunit/blob/969d9f7e887836f01a6c525324bf3db55658c28f/STAExamples/WpfFactAttribute.cs // Similar to WpfFactAttribute https://github.com/xunit/samples.xunit/blob/969d9f7e887836f01a6c525324bf3db55658c28f/STAExamples/WpfFactAttribute.cs
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
[XunitTestCaseDiscoverer("Xunit.ForegroundFactAttribute", "Microsoft.VisualStudio.LanguageServices.Razor")] [XunitTestCaseDiscoverer("Xunit.ForegroundFactDiscoverer", "Microsoft.VisualStudio.LanguageServices.Razor.Test")]
internal class ForegroundFactAttribute : FactAttribute internal class ForegroundFactAttribute : FactAttribute
{ {
} }

View File

@ -1,117 +1,25 @@
// Copyright (c) .NET Foundation. All rights reserved. // Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel; using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Threading;
using Xunit.Abstractions; using Xunit.Abstractions;
using Xunit.Sdk; using Xunit.Sdk;
namespace Xunit namespace Xunit
{ {
internal class ForegroundFactTestCase : LongLivedMarshalByRefObject, IXunitTestCase internal class ForegroundFactDiscoverer : IXunitTestCaseDiscoverer
{ {
private IXunitTestCase _inner; readonly FactDiscoverer _inner;
[EditorBrowsable(EditorBrowsableState.Never)] public ForegroundFactDiscoverer(IMessageSink diagnosticMessageSink)
[Obsolete("Called by the de-serializer", error: true)]
public ForegroundFactTestCase()
{ {
_inner = new FactDiscoverer(diagnosticMessageSink);
} }
public ForegroundFactTestCase(IXunitTestCase testCase) public IEnumerable<IXunitTestCase> Discover(ITestFrameworkDiscoveryOptions discoveryOptions, ITestMethod testMethod, IAttributeInfo factAttribute)
{ {
_inner = testCase; return _inner.Discover(discoveryOptions, testMethod, factAttribute).Select(t => new ForegroundFactTestCase(t));
}
public string DisplayName => _inner.DisplayName;
public IMethodInfo Method => _inner.Method;
public string SkipReason => _inner.SkipReason;
public ISourceInformation SourceInformation
{
get => _inner.SourceInformation;
set => _inner.SourceInformation = value;
}
public ITestMethod TestMethod => _inner.TestMethod;
public object[] TestMethodArguments => _inner.TestMethodArguments;
public Dictionary<string, List<string>> Traits => _inner.Traits;
public string UniqueID => _inner.UniqueID;
public void Deserialize(IXunitSerializationInfo info)
{
_inner = info.GetValue<IXunitTestCase>("InnerTestCase");
}
public void Serialize(IXunitSerializationInfo info)
{
info.AddValue("InnerTestCase", _inner);
}
public Task<RunSummary> RunAsync(
IMessageSink diagnosticMessageSink,
IMessageBus messageBus,
object[] constructorArguments,
ExceptionAggregator aggregator,
CancellationTokenSource cancellationTokenSource)
{
var tcs = new TaskCompletionSource<RunSummary>();
var thread = new Thread(() =>
{
try
{
SynchronizationContext.SetSynchronizationContext(new DispatcherSynchronizationContext());
var worker = _inner.RunAsync(diagnosticMessageSink, messageBus, constructorArguments, aggregator, cancellationTokenSource);
Exception caught = null;
var frame = new DispatcherFrame();
Task.Run(async () =>
{
try
{
await worker;
}
catch (Exception ex)
{
caught = ex;
}
finally
{
frame.Continue = false;
}
});
Dispatcher.PushFrame(frame);
if (caught == null)
{
tcs.SetException(caught);
}
else
{
tcs.SetResult(worker.Result);
}
}
catch (Exception e)
{
tcs.SetException(e);
}
});
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
return tcs.Task;
} }
} }
} }

View File

@ -0,0 +1,117 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Threading;
using Xunit.Abstractions;
using Xunit.Sdk;
namespace Xunit
{
internal class ForegroundFactTestCase : LongLivedMarshalByRefObject, IXunitTestCase
{
private IXunitTestCase _inner;
[EditorBrowsable(EditorBrowsableState.Never)]
[Obsolete("Called by the de-serializer", error: true)]
public ForegroundFactTestCase()
{
}
public ForegroundFactTestCase(IXunitTestCase testCase)
{
_inner = testCase;
}
public string DisplayName => _inner.DisplayName;
public IMethodInfo Method => _inner.Method;
public string SkipReason => _inner.SkipReason;
public ISourceInformation SourceInformation
{
get => _inner.SourceInformation;
set => _inner.SourceInformation = value;
}
public ITestMethod TestMethod => _inner.TestMethod;
public object[] TestMethodArguments => _inner.TestMethodArguments;
public Dictionary<string, List<string>> Traits => _inner.Traits;
public string UniqueID => _inner.UniqueID;
public void Deserialize(IXunitSerializationInfo info)
{
_inner = info.GetValue<IXunitTestCase>("InnerTestCase");
}
public void Serialize(IXunitSerializationInfo info)
{
info.AddValue("InnerTestCase", _inner);
}
public Task<RunSummary> RunAsync(
IMessageSink diagnosticMessageSink,
IMessageBus messageBus,
object[] constructorArguments,
ExceptionAggregator aggregator,
CancellationTokenSource cancellationTokenSource)
{
var tcs = new TaskCompletionSource<RunSummary>();
var thread = new Thread(() =>
{
try
{
SynchronizationContext.SetSynchronizationContext(new DispatcherSynchronizationContext());
var worker = _inner.RunAsync(diagnosticMessageSink, messageBus, constructorArguments, aggregator, cancellationTokenSource);
Exception caught = null;
var frame = new DispatcherFrame();
Task.Run(async () =>
{
try
{
await worker;
}
catch (Exception ex)
{
caught = ex;
}
finally
{
frame.Continue = false;
}
});
Dispatcher.PushFrame(frame);
if (caught == null)
{
tcs.SetResult(worker.Result);
}
else
{
tcs.SetException(caught);
}
}
catch (Exception e)
{
tcs.SetException(e);
}
});
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
return tcs.Task;
}
}
}