From ae925049bb8923dcf8ca9393889c67ef850529b0 Mon Sep 17 00:00:00 2001 From: Ryan Nowak Date: Wed, 30 Aug 2017 16:59:30 -0700 Subject: [PATCH] Add an example --- ...faultVisualStudioDocumentTrackerFactory.cs | 25 +++- ...tVisualStudioDocumentTrackerFactoryTest.cs | 38 +++--- .../Xunit/ForegroundFactAttribute.cs | 2 +- .../Xunit/ForegroundFactDiscoverer.cs | 108 ++-------------- .../Xunit/ForegroundFactTestCase.cs | 117 ++++++++++++++++++ 5 files changed, 168 insertions(+), 122 deletions(-) create mode 100644 test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Xunit/ForegroundFactTestCase.cs diff --git a/src/Microsoft.VisualStudio.LanguageServices.Razor/Editor/DefaultVisualStudioDocumentTrackerFactory.cs b/src/Microsoft.VisualStudio.LanguageServices.Razor/Editor/DefaultVisualStudioDocumentTrackerFactory.cs index de000a998c..24bfd0667c 100644 --- a/src/Microsoft.VisualStudio.LanguageServices.Razor/Editor/DefaultVisualStudioDocumentTrackerFactory.cs +++ b/src/Microsoft.VisualStudio.LanguageServices.Razor/Editor/DefaultVisualStudioDocumentTrackerFactory.cs @@ -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. // diff --git a/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Editor/DefaultVisualStudioDocumentTrackerFactoryTest.cs b/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Editor/DefaultVisualStudioDocumentTrackerFactoryTest.cs index 1025ed98f7..be5e1b710d 100644 --- a/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Editor/DefaultVisualStudioDocumentTrackerFactoryTest.cs +++ b/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Editor/DefaultVisualStudioDocumentTrackerFactoryTest.cs @@ -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( p => p.FindProject(It.IsAny()) == Mock.Of() && @@ -32,11 +32,11 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor private IContentType NonRazorContentType { get; } = Mock.Of(c => c.IsOfType(It.IsAny()) == 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(); @@ -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(); @@ -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(); @@ -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(); @@ -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(); var textView2 = Mock.Of(); @@ -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(); var textView2 = Mock.Of(); @@ -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(); @@ -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() { @@ -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(); diff --git a/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Xunit/ForegroundFactAttribute.cs b/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Xunit/ForegroundFactAttribute.cs index b692a43a8f..69a5984910 100644 --- a/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Xunit/ForegroundFactAttribute.cs +++ b/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Xunit/ForegroundFactAttribute.cs @@ -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 { } diff --git a/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Xunit/ForegroundFactDiscoverer.cs b/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Xunit/ForegroundFactDiscoverer.cs index 02b0e0c383..f54586b4c4 100644 --- a/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Xunit/ForegroundFactDiscoverer.cs +++ b/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Xunit/ForegroundFactDiscoverer.cs @@ -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 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> Traits => _inner.Traits; - - public string UniqueID => _inner.UniqueID; - - public void Deserialize(IXunitSerializationInfo info) - { - _inner = info.GetValue("InnerTestCase"); - } - - public void Serialize(IXunitSerializationInfo info) - { - info.AddValue("InnerTestCase", _inner); - } - - public Task RunAsync( - IMessageSink diagnosticMessageSink, - IMessageBus messageBus, - object[] constructorArguments, - ExceptionAggregator aggregator, - CancellationTokenSource cancellationTokenSource) - { - var tcs = new TaskCompletionSource(); - 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)); } } -} +} \ No newline at end of file diff --git a/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Xunit/ForegroundFactTestCase.cs b/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Xunit/ForegroundFactTestCase.cs new file mode 100644 index 0000000000..dd0bec5456 --- /dev/null +++ b/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Xunit/ForegroundFactTestCase.cs @@ -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> Traits => _inner.Traits; + + public string UniqueID => _inner.UniqueID; + + public void Deserialize(IXunitSerializationInfo info) + { + _inner = info.GetValue("InnerTestCase"); + } + + public void Serialize(IXunitSerializationInfo info) + { + info.AddValue("InnerTestCase", _inner); + } + + public Task RunAsync( + IMessageSink diagnosticMessageSink, + IMessageBus messageBus, + object[] constructorArguments, + ExceptionAggregator aggregator, + CancellationTokenSource cancellationTokenSource) + { + var tcs = new TaskCompletionSource(); + 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; + } + } +}