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))]
internal class DefaultVisualStudioDocumentTrackerFactory : VisualStudioDocumentTrackerFactory, IWpfTextViewConnectionListener
{
private readonly ForegroundDispatcher _foregroundDispatcher;
private readonly ProjectSnapshotManager _projectManager;
private readonly TextBufferProjectService _projectService;
private readonly Workspace _workspace;
[ImportingConstructor]
public DefaultVisualStudioDocumentTrackerFactory(
ForegroundDispatcher foregroundDispatcher,
TextBufferProjectService projectService,
[Import(typeof(VisualStudioWorkspace))] Workspace workspace)
{
if (foregroundDispatcher == null)
{
throw new ArgumentNullException(nameof(foregroundDispatcher));
}
if (projectService == null)
{
throw new ArgumentNullException(nameof(projectService));
@ -39,7 +46,8 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
{
throw new ArgumentNullException(nameof(workspace));
}
_foregroundDispatcher = foregroundDispatcher;
_projectService = projectService;
_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.
internal DefaultVisualStudioDocumentTrackerFactory(
ForegroundDispatcher foregroundDispatcher,
ProjectSnapshotManager projectManager,
TextBufferProjectService projectService,
[Import(typeof(VisualStudioWorkspace))] Workspace workspace)
{
if (foregroundDispatcher == null)
{
throw new ArgumentNullException(nameof(foregroundDispatcher));
}
if (projectManager == null)
{
throw new ArgumentNullException(nameof(projectManager));
@ -67,6 +81,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
throw new ArgumentNullException(nameof(workspace));
}
_foregroundDispatcher = foregroundDispatcher;
_projectManager = projectManager;
_projectService = projectService;
_workspace = workspace;
@ -81,6 +96,8 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
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
// 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.
@ -113,7 +130,9 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
{
throw new ArgumentNullException(nameof(subjectBuffers));
}
_foregroundDispatcher.AssertForegroundThread();
for (var i = 0; i < subjectBuffers.Count; i++)
{
var textBuffer = subjectBuffers[i];
@ -148,6 +167,8 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
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
// 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
{
public class DefaultVisualStudioDocumentTrackerFactoryTest
public class DefaultVisualStudioDocumentTrackerFactoryTest : ForegroundDispatcherTestBase
{
private ProjectSnapshotManager ProjectManager { get; } = Mock.Of<ProjectSnapshotManager>(
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);
[Fact]
[ForegroundFact]
public void SubjectBuffersConnected_ForNonRazorTextBuffer_DoesNothing()
{
// Arrange
var factory = new DefaultVisualStudioDocumentTrackerFactory(ProjectManager, ProjectService, Workspace);
var factory = new DefaultVisualStudioDocumentTrackerFactory(Dispatcher, ProjectManager, ProjectService, Workspace);
var textView = Mock.Of<IWpfTextView>();
@ -52,11 +52,11 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
Assert.False(buffers[0].Properties.ContainsProperty(typeof(VisualStudioDocumentTracker)));
}
[Fact]
[ForegroundFact]
public void SubjectBuffersConnected_ForRazorTextBufferWithoutTracker_CreatesTrackerAndTracksTextView()
{
// Arrange
var factory = new DefaultVisualStudioDocumentTrackerFactory(ProjectManager, ProjectService, Workspace);
var factory = new DefaultVisualStudioDocumentTrackerFactory(Dispatcher, ProjectManager, ProjectService, Workspace);
var textView = Mock.Of<IWpfTextView>();
@ -74,11 +74,11 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
Assert.Equal(buffers[0], tracker.TextBuffer);
}
[Fact]
[ForegroundFact]
public void SubjectBuffersConnected_ForRazorTextBufferWithoutTracker_CreatesTrackerAndTracksTextView_ForMultipleBuffers()
{
// Arrange
var factory = new DefaultVisualStudioDocumentTrackerFactory(ProjectManager, ProjectService, Workspace);
var factory = new DefaultVisualStudioDocumentTrackerFactory(Dispatcher, ProjectManager, ProjectService, Workspace);
var textView = Mock.Of<IWpfTextView>();
@ -104,11 +104,11 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
Assert.Equal(buffers[2], tracker.TextBuffer);
}
[Fact]
[ForegroundFact]
public void SubjectBuffersConnected_ForRazorTextBufferWithTracker_DoesNotAddDuplicateTextViewEntry()
{
// Arrange
var factory = new DefaultVisualStudioDocumentTrackerFactory(ProjectManager, ProjectService, Workspace);
var factory = new DefaultVisualStudioDocumentTrackerFactory(Dispatcher, ProjectManager, ProjectService, Workspace);
var textView = Mock.Of<IWpfTextView>();
@ -130,11 +130,11 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
Assert.Collection(tracker.TextViews, v => Assert.Same(v, textView));
}
[Fact]
[ForegroundFact]
public void SubjectBuffersConnected_ForRazorTextBufferWithTracker_AddsEntryForADifferentTextView()
{
// Arrange
var factory = new DefaultVisualStudioDocumentTrackerFactory(ProjectManager, ProjectService, Workspace);
var factory = new DefaultVisualStudioDocumentTrackerFactory(Dispatcher, ProjectManager, ProjectService, Workspace);
var textView1 = 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));
}
[Fact]
[ForegroundFact]
public void SubjectBuffersDisconnected_ForAnyTextBufferWithTracker_RemovesTextView()
{
// Arrange
var factory = new DefaultVisualStudioDocumentTrackerFactory(ProjectManager, ProjectService, Workspace);
var factory = new DefaultVisualStudioDocumentTrackerFactory(Dispatcher, ProjectManager, ProjectService, Workspace);
var textView1 = 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));
}
[Fact]
[ForegroundFact]
public void SubjectBuffersDisconnected_ForAnyTextBufferWithoutTracker_DoesNothing()
{
// Arrange
var factory = new DefaultVisualStudioDocumentTrackerFactory(ProjectManager, ProjectService, Workspace);
var factory = new DefaultVisualStudioDocumentTrackerFactory(Dispatcher, ProjectManager, ProjectService, Workspace);
var textView = Mock.Of<IWpfTextView>();
@ -214,11 +214,11 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
Assert.False(buffers[0].Properties.ContainsProperty(typeof(VisualStudioDocumentTracker)));
}
[Fact]
[ForegroundFact]
public void GetTracker_ForRazorTextBufferWithTracker_ReturnsTheFirstTracker()
{
// Arrange
var factory = new DefaultVisualStudioDocumentTrackerFactory(ProjectManager, ProjectService, Workspace);
var factory = new DefaultVisualStudioDocumentTrackerFactory(Dispatcher, ProjectManager, ProjectService, Workspace);
var buffers = new Collection<ITextBuffer>()
{
@ -241,11 +241,11 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
Assert.Same(tracker, result);
}
[Fact]
[ForegroundFact]
public void GetTracker_WithoutRazorBuffer_ReturnsNull()
{
// Arrange
var factory = new DefaultVisualStudioDocumentTrackerFactory(ProjectManager, ProjectService, Workspace);
var factory = new DefaultVisualStudioDocumentTrackerFactory(Dispatcher, ProjectManager, ProjectService, Workspace);
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
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
[XunitTestCaseDiscoverer("Xunit.ForegroundFactAttribute", "Microsoft.VisualStudio.LanguageServices.Razor")]
[XunitTestCaseDiscoverer("Xunit.ForegroundFactDiscoverer", "Microsoft.VisualStudio.LanguageServices.Razor.Test")]
internal class ForegroundFactAttribute : FactAttribute
{
}

View File

@ -1,117 +1,25 @@
// 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 System.Linq;
using Xunit.Abstractions;
using Xunit.Sdk;
namespace Xunit
{
internal class ForegroundFactTestCase : LongLivedMarshalByRefObject, IXunitTestCase
internal class ForegroundFactDiscoverer : IXunitTestCaseDiscoverer
{
private IXunitTestCase _inner;
readonly FactDiscoverer _inner;
[EditorBrowsable(EditorBrowsableState.Never)]
[Obsolete("Called by the de-serializer", error: true)]
public ForegroundFactTestCase()
public ForegroundFactDiscoverer(IMessageSink diagnosticMessageSink)
{
_inner = new FactDiscoverer(diagnosticMessageSink);
}
public ForegroundFactTestCase(IXunitTestCase testCase)
public IEnumerable<IXunitTestCase> Discover(ITestFrameworkDiscoveryOptions discoveryOptions, ITestMethod testMethod, IAttributeInfo factAttribute)
{
_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.SetException(caught);
}
else
{
tcs.SetResult(worker.Result);
}
}
catch (Exception e)
{
tcs.SetException(e);
}
});
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
return tcs.Task;
return _inner.Discover(discoveryOptions, testMethod, factAttribute).Select(t => new ForegroundFactTestCase(t));
}
}
}
}

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;
}
}
}