未验证 提交 d5959929 编写于 作者: S Sam Harwell 提交者: GitHub

Merge pull request #26017 from sharwell/wpf-theory

Implement WpfTheory
......@@ -2179,7 +2179,7 @@ public async Task TestCreateWithBufferNotInWorkspace()
var contentType = contentTypeService.GetDefaultContentType();
var extraBuffer = workspace.ExportProvider.GetExportedValue<ITextBufferFactoryService>().CreateTextBuffer("", contentType);
WpfTestCase.RequireWpfFact("Creates an IWpfTextView explicitly with an unrelated buffer");
WpfTestRunner.RequireWpfFact($"Creates an {nameof(IWpfTextView)} explicitly with an unrelated buffer");
using (var disposableView = workspace.ExportProvider.GetExportedValue<ITextEditorFactoryService>().CreateDisposableTextView(extraBuffer))
{
var listenerProvider = workspace.ExportProvider.GetExportedValue<IAsynchronousOperationListenerProvider>();
......
......@@ -213,7 +213,7 @@ public async Task TestPreviewDiagnosticTaggerInPreviewPane()
var newDocument = oldDocument.WithText(oldText.WithChanges(new TextChange(new TextSpan(0, oldText.Length), "class C { }")));
// create a diff view
WpfTestCase.RequireWpfFact($"{nameof(TestPreviewDiagnosticTaggerInPreviewPane)} creates a {nameof(DifferenceViewerPreview)}");
WpfTestRunner.RequireWpfFact($"{nameof(TestPreviewDiagnosticTaggerInPreviewPane)} creates a {nameof(DifferenceViewerPreview)}");
var previewFactoryService = workspace.ExportProvider.GetExportedValue<IPreviewFactoryService>();
using (var diffView = (DifferenceViewerPreview)(await previewFactoryService.CreateChangedDocumentPreviewViewAsync(oldDocument, newDocument, CancellationToken.None)))
......
......@@ -58,7 +58,7 @@ List<ITagSpan<TestTag>> tagProducer(SnapshotSpan span, CancellationToken cancell
var asyncListener = new AsynchronousOperationListener();
WpfTestCase.RequireWpfFact($"{nameof(AsynchronousTaggerTests)}.{nameof(LargeNumberOfSpans)} creates asynchronous taggers");
WpfTestRunner.RequireWpfFact($"{nameof(AsynchronousTaggerTests)}.{nameof(LargeNumberOfSpans)} creates asynchronous taggers");
var notificationService = workspace.GetService<IForegroundNotificationService>();
......@@ -96,7 +96,7 @@ public void TestSynchronousOutlining()
{
using (var workspace = TestWorkspace.CreateCSharp("class Program {\r\n\r\n}"))
{
WpfTestCase.RequireWpfFact($"{nameof(AsynchronousTaggerTests)}.{nameof(TestSynchronousOutlining)} creates asynchronous taggers");
WpfTestRunner.RequireWpfFact($"{nameof(AsynchronousTaggerTests)}.{nameof(TestSynchronousOutlining)} creates asynchronous taggers");
var tagProvider = new VisualStudio14StructureTaggerProvider(
workspace.GetService<IForegroundNotificationService>(),
......
// Copyright (c) Microsoft. 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.Linq;
using System.Threading;
using System.Windows.Threading;
using Microsoft.CodeAnalysis.Editor.Shared.Threading;
using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces;
using Microsoft.CodeAnalysis.Shared.TestHooks;
......@@ -18,7 +16,7 @@ public class AsynchronousWorkerTests
public AsynchronousWorkerTests()
{
WpfTestCase.RequireWpfFact($"Tests are testing {nameof(AsynchronousSerialWorkQueue)} which is designed to run methods on the UI thread");
WpfTestRunner.RequireWpfFact($"Tests are testing {nameof(AsynchronousSerialWorkQueue)} which is designed to run methods on the UI thread");
TestWorkspace.ResetThreadAffinity();
_foregroundSyncContext = SynchronizationContext.Current;
Assert.NotNull(_foregroundSyncContext);
......
......@@ -25,7 +25,7 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.KeywordHighlighting
workspace.Options = workspace.Options.WithChangedOption(FeatureOnOffOptions.KeywordHighlighting, document.Project.Language, optionIsEnabled)
WpfTestCase.RequireWpfFact($"{NameOf(AbstractKeywordHighlightingTests)}.VerifyHighlightsAsync creates asynchronous taggers")
WpfTestRunner.RequireWpfFact($"{NameOf(AbstractKeywordHighlightingTests)}.{NameOf(Me.VerifyHighlightsAsync)} creates asynchronous taggers")
Dim highlightingService = workspace.GetService(Of IHighlightingService)()
Dim tagProducer = New HighlighterViewTaggerProvider(
......
......@@ -24,7 +24,7 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.ReferenceHighlighting
Private Async Function VerifyHighlightsAsync(test As XElement, optionIsEnabled As Boolean, outOfProcess As Boolean) As Tasks.Task
Using workspace = TestWorkspace.Create(test)
WpfTestCase.RequireWpfFact($"{NameOf(AbstractReferenceHighlightingTests)}.VerifyHighlightsAsync creates asynchronous taggers")
WpfTestRunner.RequireWpfFact($"{NameOf(AbstractReferenceHighlightingTests)}.{NameOf(Me.VerifyHighlightsAsync)} creates asynchronous taggers")
workspace.Options = workspace.Options.WithChangedOption(RemoteHostOptions.RemoteHostTest, outOfProcess).
WithChangedOption(RemoteFeatureOptions.OutOfProcessAllowed, outOfProcess).
......
......@@ -22,7 +22,7 @@ protected async Task TestBraceHighlightingAsync(string markup, ParseOptions opti
{
using (var workspace = CreateWorkspace(markup, options))
{
WpfTestCase.RequireWpfFact($"{nameof(AbstractBraceHighlightingTests)}.{nameof(TestBraceHighlightingAsync)} creates asynchronous taggers");
WpfTestRunner.RequireWpfFact($"{nameof(AbstractBraceHighlightingTests)}.{nameof(TestBraceHighlightingAsync)} creates asynchronous taggers");
var provider = new BraceHighlightingViewTaggerProvider(
GetBraceMatchingService(workspace),
......
......@@ -14,6 +14,7 @@
using Microsoft.CodeAnalysis.VisualBasic;
using Microsoft.CodeAnalysis.VisualBasic.ChangeSignature;
using Microsoft.VisualStudio.Composition;
using Roslyn.Test.Utilities;
namespace Microsoft.CodeAnalysis.Editor.UnitTests.ChangeSignature
{
......@@ -66,7 +67,7 @@ public TestChangeSignatureOptionsService TestChangeSignatureOptionsService
public ChangeSignatureResult ChangeSignature()
{
Roslyn.Test.Utilities.WpfTestCase.RequireWpfFact($"{nameof(AbstractChangeSignatureService.ChangeSignature)} currently needs to run on a WPF Fact because it's factored in a way that tries popping up UI in some cases.");
WpfTestRunner.RequireWpfFact($"{nameof(AbstractChangeSignatureService.ChangeSignature)} currently needs to run on a WPF Fact because it's factored in a way that tries popping up UI in some cases.");
return ChangeSignatureService.ChangeSignature(
InvocationDocument,
......
......@@ -9,7 +9,6 @@
using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces;
using Microsoft.CodeAnalysis.Shared.TestHooks;
using Microsoft.CodeAnalysis.SolutionCrawler;
using Microsoft.VisualStudio.Text.Classification;
using Microsoft.VisualStudio.Text.Tagging;
using Roslyn.Test.Utilities;
......@@ -96,7 +95,7 @@ public ITaggerProvider TaggerProvider
{
if (_taggerProvider == null)
{
WpfTestCase.RequireWpfFact($"{nameof(DiagnosticTaggerWrapper<TProvider>)}.{nameof(TaggerProvider)} creates asynchronous taggers");
WpfTestRunner.RequireWpfFact($"{nameof(DiagnosticTaggerWrapper<TProvider>)}.{nameof(TaggerProvider)} creates asynchronous taggers");
if (typeof(TProvider) == typeof(DiagnosticsSquiggleTaggerProvider))
{
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Text;
using System.Threading;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.Editor.UnitTests;
using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces;
using Microsoft.VisualStudio.Composition;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Editor;
using Microsoft.VisualStudio.Utilities;
using Roslyn.Test.Utilities;
using Microsoft.CodeAnalysis.Editor.UnitTests;
namespace Roslyn.Test.EditorUtilities
{
......@@ -47,7 +45,7 @@ public static class EditorFactory
params string[] lines)
{
TestWorkspace.ResetThreadAffinity();
WpfTestCase.RequireWpfFact($"Creates an IWpfTextView through {nameof(EditorFactory)}.{nameof(CreateView)}");
WpfTestRunner.RequireWpfFact($"Creates an {nameof(IWpfTextView)} through {nameof(EditorFactory)}.{nameof(CreateView)}");
var buffer = CreateBuffer(contentType, exportProvider, lines);
return exportProvider.GetExportedValue<ITextEditorFactoryService>().CreateDisposableTextView(buffer);
......
......@@ -9,7 +9,6 @@ public class ConditionalWpfFactAttribute : WpfFactAttribute
public ConditionalWpfFactAttribute(Type skipCondition)
{
var condition = Activator.CreateInstance(skipCondition) as ExecutionCondition;
if (condition.ShouldSkip)
{
Skip = condition.SkipReason;
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
namespace Roslyn.Test.Utilities
{
public class ConditionalWpfTheoryAttribute : WpfTheoryAttribute
{
public ConditionalWpfTheoryAttribute(Type skipCondition)
{
var condition = Activator.CreateInstance(skipCondition) as ExecutionCondition;
if (condition.ShouldSkip)
{
Skip = condition.SkipReason;
}
}
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
// ParallelExtensionsExtras: https://code.msdn.microsoft.com/ParExtSamples
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Threading;
namespace Roslyn.Test.Utilities
{
/// <summary>Provides a scheduler that uses STA threads.</summary>
public sealed class StaTaskScheduler : TaskScheduler, IDisposable
public sealed class StaTaskScheduler : IDisposable
{
/// <summary>Gets a StaTaskScheduler for the current AppDomain.</summary>
/// <remarks>We use a count of 1, because the editor ends up re-using <see cref="System.Windows.Threading.DispatcherObject"/>
/// <summary>Gets a <see cref="StaTaskScheduler"/> for the current <see cref="AppDomain"/>.</summary>
/// <remarks>We use a count of 1, because the editor ends up re-using <see cref="DispatcherObject"/>
/// instances between tests, so we need to always use the same thread for our Sta tests.</remarks>
public static StaTaskScheduler DefaultSta { get; } = new StaTaskScheduler(1);
/// <summary>Stores the queued tasks to be executed by our pool of STA threads.</summary>
private BlockingCollection<Task> _tasks;
public static StaTaskScheduler DefaultSta { get; } = new StaTaskScheduler();
/// <summary>The STA threads used by the scheduler.</summary>
private readonly ImmutableArray<Thread> _threads;
public Thread StaThread { get; }
public ImmutableArray<Thread> Threads => _threads;
public bool IsRunningInScheduler => StaThread.ManagedThreadId == Thread.CurrentThread.ManagedThreadId;
/// <summary>Initializes a new instance of the StaTaskScheduler class with the specified concurrency level.</summary>
/// <param name="numberOfThreads">The number of threads that should be created and used by this scheduler.</param>
public StaTaskScheduler(int numberOfThreads)
/// <summary>Initializes a new instance of the <see cref="StaTaskScheduler"/> class.</summary>
public StaTaskScheduler()
{
// Validate arguments
if (numberOfThreads < 1)
throw new ArgumentOutOfRangeException(nameof(numberOfThreads));
// Initialize the tasks collection
_tasks = new BlockingCollection<Task>();
// Create the threads to be used by this scheduler
_threads = Enumerable.Range(0, numberOfThreads).Select(i =>
using (var threadStartedEvent = new ManualResetEventSlim(initialState: false))
{
var thread = new Thread(() =>
DispatcherSynchronizationContext synchronizationContext = null;
StaThread = new Thread(() =>
{
// Continually get the next task and try to execute it.
// This will continue until the scheduler is disposed and no more tasks remain.
foreach (var t in _tasks.GetConsumingEnumerable())
var oldContext = SynchronizationContext.Current;
try
{
if (!TryExecuteTask(t))
{
System.Diagnostics.Debug.Assert(t.IsCompleted, "Can't run, not completed");
}
}
});
thread.Name = $"{nameof(StaTaskScheduler)} thread";
thread.IsBackground = true;
thread.SetApartmentState(ApartmentState.STA);
return thread;
}).ToImmutableArray();
// Start all of the threads
foreach (var thread in _threads)
{
thread.Start();
}
}
internal void DoEvents()
{
if (!_threads.Contains(Thread.CurrentThread))
throw new InvalidOperationException();
// All WPF Tests need a DispatcherSynchronizationContext and we dont want to block pending keyboard
// or mouse input from the user. So use background priority which is a single level below user input.
synchronizationContext = new DispatcherSynchronizationContext();
// Continually get the next task and try to execute it.
// This will continue until the scheduler contains no more actively-scheduled tasks.
while (_tasks.TryTake(out var t))
{
if (!TryExecuteTask(t))
{
System.Diagnostics.Debug.Assert(t.IsCompleted, "Can't run, not completed");
}
}
}
/// <summary>Queues a Task to be executed by this scheduler.</summary>
/// <param name="task">The task to be executed.</param>
protected override void QueueTask(Task task)
{
// Push it into the blocking collection of tasks
_tasks.Add(task);
}
// xUnit creates its own synchronization context and wraps any existing context so that messages are
// still pumped as necessary. So we are safe setting it here, where we are not safe setting it in test.
SynchronizationContext.SetSynchronizationContext(synchronizationContext);
/// <summary>Provides a list of the scheduled tasks for the debugger to consume.</summary>
/// <returns>An enumerable of all tasks currently scheduled.</returns>
protected override IEnumerable<Task> GetScheduledTasks()
{
// Serialize the contents of the blocking collection of tasks for the debugger
return _tasks.ToArray();
}
threadStartedEvent.Set();
/// <summary>Determines whether a Task may be inlined.</summary>
/// <param name="task">The task to be executed.</param>
/// <param name="taskWasPreviouslyQueued">Whether the task was previously queued.</param>
/// <returns>true if the task was successfully inlined; otherwise, false.</returns>
protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
{
// Try to inline if the current thread is STA
return
Thread.CurrentThread.GetApartmentState() == ApartmentState.STA &&
TryExecuteTask(task);
Dispatcher.Run();
}
finally
{
SynchronizationContext.SetSynchronizationContext(oldContext);
}
});
StaThread.Name = $"{nameof(StaTaskScheduler)} thread";
StaThread.IsBackground = true;
StaThread.SetApartmentState(ApartmentState.STA);
StaThread.Start();
threadStartedEvent.Wait();
DispatcherSynchronizationContext = synchronizationContext;
};
}
/// <summary>Gets the maximum concurrency level supported by this scheduler.</summary>
public override int MaximumConcurrencyLevel
public DispatcherSynchronizationContext DispatcherSynchronizationContext
{
get
{
return _threads.Length;
}
get;
}
/// <summary>
......@@ -126,29 +67,11 @@ public override int MaximumConcurrencyLevel
/// </summary>
public void Dispose()
{
if (_tasks != null)
if (StaThread.IsAlive)
{
// Indicate that no new tasks will be coming in
_tasks.CompleteAdding();
// Wait for all threads to finish processing tasks
foreach (var thread in _threads)
thread.Join();
// Cleanup
_tasks.Dispose();
_tasks = null;
DispatcherSynchronizationContext.Post(_ => Dispatcher.ExitAllFrames(), null);
StaThread.Join();
}
}
public bool IsAnyQueued()
{
if (_threads.Length != 1 || _threads[0] != Thread.CurrentThread)
{
throw new InvalidOperationException("Operation invalid in this context");
}
return _tasks.Count > 0;
}
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Xunit;
using Xunit.Sdk;
......@@ -9,5 +8,7 @@ namespace Roslyn.Test.Utilities
{
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
[XunitTestCaseDiscoverer("Roslyn.Test.Utilities.WpfFactDiscoverer", "Roslyn.Services.Test.Utilities")]
public class WpfFactAttribute : FactAttribute { }
public class WpfFactAttribute : FactAttribute
{
}
}
// Copyright (c) Microsoft. 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.Threading;
using System.Collections.Generic;
using Xunit.Abstractions;
using Xunit.Sdk;
......@@ -10,21 +9,35 @@ namespace Roslyn.Test.Utilities
public class WpfFactDiscoverer : FactDiscoverer
{
private readonly IMessageSink _diagnosticMessageSink;
/// <summary>
/// The name of a <see cref="Semaphore"/> used to ensure that only a single
/// <see cref="WpfFactAttribute"/>-attributed test runs at once. This requirement must be made because,
/// currently, <see cref="WpfTestCase"/>'s logic sets various static state before a method runs. If two tests
/// run interleaved on the same scheduler (i.e. if one yields with an await) then all bets are off.
/// </summary>
private readonly Guid _wpfTestSerializationGate = Guid.NewGuid();
public WpfFactDiscoverer(IMessageSink diagnosticMessageSink) : base(diagnosticMessageSink)
{
_diagnosticMessageSink = diagnosticMessageSink;
}
protected override IXunitTestCase CreateTestCase(ITestFrameworkDiscoveryOptions discoveryOptions, ITestMethod testMethod, IAttributeInfo factAttribute)
=> new WpfTestCase(_diagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), testMethod, _wpfTestSerializationGate);
=> new WpfTestCase(_diagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), testMethod);
}
public class WpfTheoryDiscoverer : TheoryDiscoverer
{
private readonly IMessageSink _diagnosticMessageSink;
public WpfTheoryDiscoverer(IMessageSink diagnosticMessageSink) : base(diagnosticMessageSink)
{
_diagnosticMessageSink = diagnosticMessageSink;
}
protected override IEnumerable<IXunitTestCase> CreateTestCasesForDataRow(ITestFrameworkDiscoveryOptions discoveryOptions, ITestMethod testMethod, IAttributeInfo theoryAttribute, object[] dataRow)
{
var testCase = new WpfTestCase(_diagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), testMethod, dataRow);
return new[] { testCase };
}
protected override IEnumerable<IXunitTestCase> CreateTestCasesForTheory(ITestFrameworkDiscoveryOptions discoveryOptions, ITestMethod testMethod, IAttributeInfo theoryAttribute)
{
var testCase = new WpfTheoryTestCase(_diagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), testMethod);
return new[] { testCase };
}
}
}
......@@ -2,127 +2,39 @@
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Threading;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.Utilities;
using Xunit.Abstractions;
using Xunit.Sdk;
using Roslyn.Utilities;
namespace Roslyn.Test.Utilities
{
public class WpfTestCase : XunitTestCase
public sealed class WpfTestCase : XunitTestCase
{
private Guid _semaphoreName;
private Semaphore _wpfTestSerializationGate;
public WpfTestSharedData SharedData { get; private set; }
[EditorBrowsable(EditorBrowsableState.Never)]
[Obsolete("Called by the de-serializer; should only be called by deriving classes for de-serialization purposes")]
public WpfTestCase()
{
}
public WpfTestCase(IMessageSink diagnosticMessageSink, TestMethodDisplay defaultMethodDisplay, ITestMethod testMethod, Guid wpfTestSerializationGate, object[] testMethodArguments = null)
public WpfTestCase(IMessageSink diagnosticMessageSink, TestMethodDisplay defaultMethodDisplay, ITestMethod testMethod, object[] testMethodArguments = null)
: base(diagnosticMessageSink, defaultMethodDisplay, testMethod, testMethodArguments)
{
_semaphoreName = wpfTestSerializationGate;
_wpfTestSerializationGate = new Semaphore(1, 1, _semaphoreName.ToString("N"));
SharedData = WpfTestSharedData.Instance;
}
public override Task<RunSummary> RunAsync(IMessageSink diagnosticMessageSink, IMessageBus messageBus, object[] constructorArguments, ExceptionAggregator aggregator, CancellationTokenSource cancellationTokenSource)
{
var sta = StaTaskScheduler.DefaultSta;
var task = Task.Factory.StartNew(async () =>
{
Debug.Assert(sta.Threads.Length == 1);
Debug.Assert(sta.Threads[0] == Thread.CurrentThread);
using (await _wpfTestSerializationGate.DisposableWaitAsync(CancellationToken.None))
{
try
{
// All WPF Tests need a DispatcherSynchronizationContext and we dont want to block pending keyboard
// or mouse input from the user. So use background priority which is a single level below user input.
var dispatcherSynchronizationContext = new DispatcherSynchronizationContext();
// xUnit creates its own synchronization context and wraps any existing context so that messages are
// still pumped as necessary. So we are safe setting it here, where we are not safe setting it in test.
SynchronizationContext.SetSynchronizationContext(dispatcherSynchronizationContext);
// Sync up FTAO to the context that we are creating here.
ForegroundThreadAffinitizedObject.CurrentForegroundThreadData = new ForegroundThreadData(
Thread.CurrentThread,
new SynchronizationContextTaskScheduler(new DispatcherSynchronizationContext(Dispatcher.CurrentDispatcher, DispatcherPriority.Background)),
ForegroundThreadDataKind.StaUnitTest);
// Reset our flag ensuring that part of this test actually needs WpfFact
s_wpfFactRequirementReason = null;
// Just call back into the normal xUnit dispatch process now that we are on an STA Thread with no synchronization context.
var baseTask = base.RunAsync(diagnosticMessageSink, messageBus, constructorArguments, aggregator, cancellationTokenSource);
do
{
var delay = Task.Delay(TimeSpan.FromMilliseconds(10), cancellationTokenSource.Token);
var completed = await Task.WhenAny(baseTask, delay).ConfigureAwait(false);
if (completed == baseTask)
{
return await baseTask.ConfigureAwait(false);
}
// Schedule a task to pump messages on the UI thread.
await Task.Factory.StartNew(
() => WaitHelper.WaitForDispatchedOperationsToComplete(DispatcherPriority.ApplicationIdle),
cancellationTokenSource.Token,
TaskCreationOptions.None,
sta).ConfigureAwait(false);
}
while (true);
}
finally
{
ForegroundThreadAffinitizedObject.CurrentForegroundThreadData = null;
s_wpfFactRequirementReason = null;
// Cleanup the synchronization context even if the test is failing exceptionally
SynchronizationContext.SetSynchronizationContext(null);
}
}
}, cancellationTokenSource.Token, TaskCreationOptions.None, sta);
return task.Unwrap();
var runner = new WpfTestCaseRunner(SharedData, this, DisplayName, SkipReason, constructorArguments, TestMethodArguments, messageBus, aggregator, cancellationTokenSource);
return runner.RunAsync();
}
public override void Serialize(IXunitSerializationInfo data)
{
base.Serialize(data);
data.AddValue(nameof(_semaphoreName), _semaphoreName.ToString("N"));
}
public override void Deserialize(IXunitSerializationInfo data)
{
base.Deserialize(data);
_semaphoreName = Guid.ParseExact(data.GetValue<string>(nameof(_semaphoreName)), "N");
_wpfTestSerializationGate = new Semaphore(1, 1, _semaphoreName.ToString("N"));
}
private static string s_wpfFactRequirementReason;
/// <summary>
/// Asserts that the test is running on a <see cref="WpfFactAttribute"/> test method, and records the reason for requiring the <see cref="WpfFactAttribute"/>.
/// </summary>
public static void RequireWpfFact(string reason)
{
if (ForegroundThreadDataInfo.CurrentForegroundThreadDataKind != ForegroundThreadDataKind.StaUnitTest)
{
throw new Exception($"This test requires {nameof(WpfFactAttribute)} because '{reason}' but is missing {nameof(WpfFactAttribute)}. Either the attribute should be changed, or the reason it needs an STA thread audited.");
}
s_wpfFactRequirementReason = reason;
SharedData = WpfTestSharedData.Instance;
}
}
}
// Copyright (c) Microsoft. 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.Reflection;
using System.Threading;
using Xunit.Abstractions;
using Xunit.Sdk;
namespace Roslyn.Test.Utilities
{
public sealed class WpfTestCaseRunner : XunitTestCaseRunner
{
public WpfTestSharedData SharedData { get; }
public WpfTestCaseRunner(
WpfTestSharedData sharedData,
IXunitTestCase testCase,
string displayName,
string skipReason,
object[] constructorArguments,
object[] testMethodArguments,
IMessageBus messageBus,
ExceptionAggregator aggregator,
CancellationTokenSource cancellationTokenSource)
: base(testCase, displayName, skipReason, constructorArguments, testMethodArguments, messageBus, aggregator, cancellationTokenSource)
{
SharedData = sharedData;
}
protected override XunitTestRunner CreateTestRunner(ITest test, IMessageBus messageBus, Type testClass, object[] constructorArguments, MethodInfo testMethod, object[] testMethodArguments, string skipReason, IReadOnlyList<BeforeAfterTestAttribute> beforeAfterAttributes, ExceptionAggregator aggregator, CancellationTokenSource cancellationTokenSource)
{
var runner = new WpfTestRunner(SharedData, test, messageBus, testClass, constructorArguments, testMethod, testMethodArguments, skipReason, beforeAfterAttributes, aggregator, cancellationTokenSource);
return runner;
}
}
}
// Copyright (c) Microsoft. 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.Reflection;
using System.Threading;
using Xunit.Abstractions;
using Xunit.Sdk;
namespace Roslyn.Test.Utilities
{
public class WpfTestInvoker : XunitTestInvoker
{
public WpfTestSharedData SharedData { get; }
public WpfTestInvoker(
WpfTestSharedData sharedData,
ITest test,
IMessageBus messageBus,
Type testClass,
object[] constructorArguments,
MethodInfo testMethod,
object[] testMethodArguments,
IReadOnlyList<BeforeAfterTestAttribute> beforeAfterAttributes,
ExceptionAggregator aggregator,
CancellationTokenSource cancellationTokenSource)
: base(test, messageBus, testClass, constructorArguments, testMethod, testMethodArguments, beforeAfterAttributes, aggregator, cancellationTokenSource)
{
SharedData = sharedData;
}
protected override object CallTestMethod(object testClassInstance)
{
SharedData.MonitorActiveAsyncTestSyncContext();
return base.CallTestMethod(testClassInstance);
}
}
}
// Copyright (c) Microsoft. 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.Diagnostics;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Threading;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.Utilities;
using Roslyn.Utilities;
using Xunit.Abstractions;
using Xunit.Sdk;
namespace Roslyn.Test.Utilities
{
/// <summary>
/// This type is actually responsible for spinning up the STA context to run all of the
/// tests.
///
/// Overriding the <see cref="XunitTestInvoker"/> to setup the STA context is not the correct
/// approach. That type begins constructing types before RunAsync and hence ctors end up
/// running on the current thread vs. the STA ones. Just completely wrapping the invocation
/// here is the best case.
/// </summary>
public sealed class WpfTestRunner : XunitTestRunner
{
private static string s_wpfFactRequirementReason;
public WpfTestSharedData SharedData { get; }
public WpfTestRunner(
WpfTestSharedData sharedData,
ITest test,
IMessageBus messageBus,
Type testClass,
object[] constructorArguments,
MethodInfo testMethod,
object[] testMethodArguments,
string skipReason,
IReadOnlyList<BeforeAfterTestAttribute> beforeAfterAttributes,
ExceptionAggregator aggregator,
CancellationTokenSource cancellationTokenSource)
: base(test, messageBus, testClass, constructorArguments, testMethod, testMethodArguments, skipReason, beforeAfterAttributes, aggregator, cancellationTokenSource)
{
SharedData = sharedData;
}
protected override Task<decimal> InvokeTestMethodAsync(ExceptionAggregator aggregator)
{
SharedData.ExecutingTest(TestMethod);
var sta = StaTaskScheduler.DefaultSta;
var task = Task.Factory.StartNew(async () =>
{
Debug.Assert(sta.StaThread == Thread.CurrentThread);
using (await SharedData.TestSerializationGate.DisposableWaitAsync(CancellationToken.None))
{
try
{
Debug.Assert(SynchronizationContext.Current is DispatcherSynchronizationContext);
// Sync up FTAO to the context that we are creating here.
ForegroundThreadAffinitizedObject.CurrentForegroundThreadData = new ForegroundThreadData(
Thread.CurrentThread,
new SynchronizationContextTaskScheduler(new DispatcherSynchronizationContext(Dispatcher.CurrentDispatcher, DispatcherPriority.Background)),
ForegroundThreadDataKind.StaUnitTest);
// Reset our flag ensuring that part of this test actually needs WpfFact
s_wpfFactRequirementReason = null;
// Just call back into the normal xUnit dispatch process now that we are on an STA Thread with no synchronization context.
var invoker = new WpfTestInvoker(SharedData, Test, MessageBus, TestClass, ConstructorArguments, TestMethod, TestMethodArguments, BeforeAfterAttributes, aggregator, CancellationTokenSource);
return invoker.RunAsync().JoinUsingDispatcher(CancellationTokenSource.Token);
}
finally
{
// Cleanup the synchronization context even if the test is failing exceptionally
SynchronizationContext.SetSynchronizationContext(null);
}
}
}, CancellationTokenSource.Token, TaskCreationOptions.None, new SynchronizationContextTaskScheduler(sta.DispatcherSynchronizationContext));
return task.Unwrap();
}
/// <summary>
/// Asserts that the test is running on a <see cref="WpfFactAttribute"/> or <see cref="WpfTheoryAttribute"/>
/// test method, and records the reason for requiring the use of an STA thread.
/// </summary>
public static void RequireWpfFact(string reason)
{
if (ForegroundThreadDataInfo.CurrentForegroundThreadDataKind != ForegroundThreadDataKind.StaUnitTest)
{
throw new Exception($"This test requires {nameof(WpfFactAttribute)} because '{reason}' but is missing {nameof(WpfFactAttribute)}. Either the attribute should be changed, or the reason it needs an STA thread audited.");
}
s_wpfFactRequirementReason = reason;
}
}
}
// Copyright (c) Microsoft. 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.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Threading;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Xunit.Abstractions;
using Xunit.Sdk;
namespace Roslyn.Test.Utilities
{
[Serializable]
public sealed class WpfTestSharedData
{
internal static readonly WpfTestSharedData Instance = new WpfTestSharedData();
/// <summary>
/// The name of a <see cref="Semaphore"/> used to ensure that only a single
/// <see cref="WpfFactAttribute"/>-attributed test runs at once. This requirement must be made because,
/// currently, <see cref="WpfTestCase"/>'s logic sets various static state before a method runs. If two tests
/// run interleaved on the same scheduler (i.e. if one yields with an await) then all bets are off.
/// </summary>
internal static readonly Guid TestSerializationGateName = Guid.NewGuid();
/// <summary>
/// Holds the last 10 test cases executed: more recent test cases will occur later in the
/// list. Useful for debugging deadlocks that occur because state leak between runs.
/// </summary>
private readonly List<string> _recentTestCases = new List<string>();
private readonly ConditionalWeakTable<AsyncTestSyncContext, object> _contextTrackingTable = new ConditionalWeakTable<AsyncTestSyncContext, object>();
public Semaphore TestSerializationGate = new Semaphore(1, 1, TestSerializationGateName.ToString("N"));
private WpfTestSharedData()
{
}
public void ExecutingTest(ITestMethod testMethod)
{
var name = $"{testMethod.TestClass.Class.Name}::{testMethod.Method.Name}";
lock (_recentTestCases)
{
_recentTestCases.Add(name);
}
}
public void ExecutingTest(MethodInfo testMethod)
{
var name = $"{testMethod.DeclaringType.Name}::{testMethod.Name}";
lock (_recentTestCases)
{
_recentTestCases.Add(name);
}
}
/// <summary>
/// When a <see cref="SynchronizationContext"/> instance is used in a <see cref="WpfFactAttribute"/>
/// test it can cause a deadlock. This happens when there are posted actions that are not run and the test
/// case is non-async.
///
/// The xunit framework monitors all calls to the active <see cref="SynchronizationContext"/> and it will
/// wait on them to complete before finishing a test. Hence if anything is posted but not run the test will
/// deadlock forever waiting for this to happen.
///
/// This code monitors the use of our <see cref="SynchronizationContext"/> and attempts to
/// detect this situation and actively fail the test when it happens. The code is a hueristic and hence
/// imprecise. But is effective in finding these problmes.
/// </summary>
public void MonitorActiveAsyncTestSyncContext()
{
// To cause the test to fail we need to post an action ot the AsyncTestContext. The xunit framework
// wraps such delegates in a try / catch and fails the test if any exception occurs. This is best
// captured at the point a posted action occurs.
var asyncContext = SynchronizationContext.Current as AsyncTestSyncContext;
if (_contextTrackingTable.TryGetValue(asyncContext, out _))
{
return;
}
var dispatcher = Dispatcher.CurrentDispatcher;
void runCallbacks()
{
var fieldInfo = asyncContext.GetType().GetField("innerContext", BindingFlags.NonPublic | BindingFlags.Instance);
var innerContext = fieldInfo.GetValue(asyncContext) as SynchronizationContext;
switch (innerContext)
{
case DispatcherSynchronizationContext _:
dispatcher.DoEvents();
break;
default:
Debug.Fail($"Unrecognized context: {asyncContext.GetType()}");
break;
}
}
_contextTrackingTable.Add(asyncContext, new object());
var startTime = DateTime.UtcNow;
void checkForBad()
{
try
{
if (!asyncContext.WaitForCompletionAsync().IsCompleted)
{
var span = DateTime.UtcNow - startTime;
if (span > TimeSpan.FromSeconds(30) && !Debugger.IsAttached)
{
asyncContext?.Post(_ => throw new Exception($"Unfulfilled {nameof(SynchronizationContext)} detected"), null);
runCallbacks();
}
else
{
var timer = new Task(() =>
{
Thread.Sleep(TimeSpan.FromSeconds(2));
queueCheckForBad();
});
timer.Start(TaskScheduler.Default);
}
}
}
catch (Exception ex)
{
Debug.Fail($"Exception monitoring {nameof(SynchronizationContext)}: {ex.Message}");
}
}
void queueCheckForBad()
{
var task = new Task((Action)checkForBad);
task.Start(new SynchronizationContextTaskScheduler(StaTaskScheduler.DefaultSta.DispatcherSynchronizationContext));
}
queueCheckForBad();
}
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Xunit;
using Xunit.Sdk;
namespace Roslyn.Test.Utilities
{
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
[XunitTestCaseDiscoverer("Roslyn.Test.Utilities.WpfTheoryDiscoverer", "Roslyn.Services.Test.Utilities")]
public class WpfTheoryAttribute : TheoryAttribute
{
}
}
// Copyright (c) Microsoft. 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.ComponentModel;
using System.Threading;
using System.Threading.Tasks;
using Xunit.Abstractions;
using Xunit.Sdk;
namespace Roslyn.Test.Utilities
{
public class WpfTheoryTestCase : XunitTheoryTestCase
{
public WpfTestSharedData SharedData { get; private set; }
[EditorBrowsable(EditorBrowsableState.Never)]
[Obsolete("Called by the de-serializer; should only be called by deriving classes for de-serialization purposes")]
public WpfTheoryTestCase() { }
public WpfTheoryTestCase(IMessageSink diagnosticMessageSink, TestMethodDisplay defaultMethodDisplay, ITestMethod testMethod)
: base(diagnosticMessageSink, defaultMethodDisplay, testMethod)
{
SharedData = WpfTestSharedData.Instance;
}
public override void Deserialize(IXunitSerializationInfo data)
{
base.Deserialize(data);
SharedData = WpfTestSharedData.Instance;
}
public override Task<RunSummary> RunAsync(IMessageSink diagnosticMessageSink, IMessageBus messageBus, object[] constructorArguments, ExceptionAggregator aggregator, CancellationTokenSource cancellationTokenSource)
{
var runner = new WpfTheoryTestCaseRunner(SharedData, this, DisplayName, SkipReason, constructorArguments, diagnosticMessageSink, messageBus, aggregator, cancellationTokenSource);
return runner.RunAsync();
}
}
}
// Copyright (c) Microsoft. 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.Reflection;
using System.Threading;
using Xunit.Abstractions;
using Xunit.Sdk;
namespace Roslyn.Test.Utilities
{
public class WpfTheoryTestCaseRunner : XunitTheoryTestCaseRunner
{
public WpfTestSharedData SharedData { get; }
public WpfTheoryTestCaseRunner(
WpfTestSharedData sharedData,
IXunitTestCase testCase,
string displayName,
string skipReason,
object[] constructorArguments,
IMessageSink diagnosticMessageSink,
IMessageBus messageBus,
ExceptionAggregator aggregator,
CancellationTokenSource cancellationTokenSource)
: base(testCase, displayName, skipReason, constructorArguments, diagnosticMessageSink, messageBus, aggregator, cancellationTokenSource)
{
SharedData = sharedData;
}
protected override XunitTestRunner CreateTestRunner(ITest test, IMessageBus messageBus, Type testClass, object[] constructorArguments, MethodInfo testMethod, object[] testMethodArguments, string skipReason, IReadOnlyList<BeforeAfterTestAttribute> beforeAfterAttributes, ExceptionAggregator aggregator, CancellationTokenSource cancellationTokenSource)
{
var runner = new WpfTestRunner(SharedData, test, messageBus, testClass, constructorArguments, testMethod, testMethodArguments, skipReason, beforeAfterAttributes, aggregator, cancellationTokenSource);
return runner;
}
}
}
......@@ -11,7 +11,6 @@
using Microsoft.VisualStudio.Composition;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Editor;
using Microsoft.VisualStudio.Utilities;
using Roslyn.Test.Utilities;
using Roslyn.Utilities;
......@@ -201,7 +200,7 @@ public IWpfTextView GetTextView()
{
TestWorkspace.ResetThreadAffinity();
WpfTestCase.RequireWpfFact($"Creates an IWpfTextView through {nameof(TestHostDocument)}.{nameof(GetTextView)}");
WpfTestRunner.RequireWpfFact($"Creates an {nameof(IWpfTextView)} through {nameof(TestHostDocument)}.{nameof(GetTextView)}");
var factory = _exportProvider.GetExportedValue<ITextEditorFactoryService>();
......
......@@ -20,7 +20,7 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.BraceMatching
End Function
Private Async Function ProduceTagsAsync(workspace As TestWorkspace, buffer As ITextBuffer, position As Integer) As Tasks.Task(Of IEnumerable(Of ITagSpan(Of BraceHighlightTag)))
WpfTestCase.RequireWpfFact($"{NameOf(BraceHighlightingTests)}.{"ProduceTagsAsync"} creates asynchronous taggers")
WpfTestRunner.RequireWpfFact($"{NameOf(BraceHighlightingTests)}.{NameOf(Me.ProduceTagsAsync)} creates asynchronous taggers")
Dim producer = New BraceHighlightingViewTaggerProvider(
workspace.GetService(Of IBraceMatchingService),
......
......@@ -2953,7 +2953,7 @@ End Class
Return p
End Function)
WpfTestCase.RequireWpfFact("Test helper creates mocks of ITextView")
WpfTestRunner.RequireWpfFact($"Test helper creates mocks of {NameOf(ITextView)}")
Dim textView = New Mock(Of ITextView)(MockBehavior.Strict)
textView.Setup(Function(x) x.Options).Returns(TestEditorOptions.Instance)
......
......@@ -5,7 +5,9 @@
using EnvDTE;
using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces;
using Microsoft.CodeAnalysis.Test.Utilities;
using Microsoft.VisualStudio.LanguageServices.Implementation.Interop;
using Roslyn.Test.Utilities;
using SyntaxNodeKey = Microsoft.VisualStudio.LanguageServices.Implementation.CodeModel.SyntaxNodeKey;
namespace Microsoft.VisualStudio.LanguageServices.CSharp.UnitTests.CodeModel
{
......@@ -56,7 +58,7 @@ protected Microsoft.CodeAnalysis.Document GetCurrentDocument()
protected CodeElement GetCodeElement(params object[] path)
{
WpfTestCase.RequireWpfFact("Tests create CodeElements which use the affinitized CleanableWeakComHandleTable");
WpfTestRunner.RequireWpfFact($"Tests create {nameof(CodeElement)}s which use the affinitized {nameof(CleanableWeakComHandleTable<SyntaxNodeKey, CodeElement>)}");
if (path.Length == 0)
{
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.ComponentModel.Composition;
using Microsoft.VisualStudio.InteractiveWindow;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Editor;
using Microsoft.VisualStudio.Utilities;
using Roslyn.Test.Utilities;
using Microsoft.VisualStudio.InteractiveWindow;
namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Interactive
{
......@@ -28,7 +28,7 @@ public InteractiveWindowEditorsFactoryService(ITextBufferFactoryService textBuff
IWpfTextView IInteractiveWindowEditorFactoryService.CreateTextView(IInteractiveWindow window, ITextBuffer buffer, ITextViewRoleSet roles)
{
WpfTestCase.RequireWpfFact($"Creates an IWpfTextView in {nameof(InteractiveWindowEditorsFactoryService)}");
WpfTestRunner.RequireWpfFact($"Creates an {nameof(IWpfTextView)} in {nameof(InteractiveWindowEditorsFactoryService)}");
var textView = _textEditorFactoryService.CreateTextView(buffer, roles);
return _textEditorFactoryService.CreateTextViewHost(textView, false).TextView;
......
......@@ -39,7 +39,7 @@ private string GetText(AbstractOptionPreviewViewModel viewModel)
public OptionViewModelTests()
{
WpfTestCase.RequireWpfFact("Tests create WPF ViewModels and updates previews with them");
WpfTestRunner.RequireWpfFact("Tests create WPF ViewModels and updates previews with them");
}
[WpfFact, Trait(Traits.Feature, Traits.Features.Options)]
......
......@@ -2,13 +2,14 @@
Imports System.Threading.Tasks
Imports Microsoft.CodeAnalysis.Editor.UnitTests.Extensions
Imports Roslyn.Test.Utilities
Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.CodeModel
Public MustInherit Class AbstractFileCodeModelTests
Inherits AbstractCodeModelObjectTests(Of EnvDTE80.FileCodeModel2)
Protected Async Function TestOperation(code As XElement, expectedCode As XElement, operation As Action(Of EnvDTE80.FileCodeModel2)) As Task
Roslyn.Test.Utilities.WpfTestCase.RequireWpfFact($"Test calls TestOperation which means we're creating new CodeModel elements.")
WpfTestRunner.RequireWpfFact($"Test calls {NameOf(Me.TestOperation)} which means we're creating new {NameOf(EnvDTE.CodeModel)} elements.")
Using state = CreateCodeModelTestState(GetWorkspaceDefinition(code))
Dim fileCodeModel = state.FileCodeModel
......
......@@ -215,7 +215,7 @@ End Class
End Function
Private Async Function TestAddAttributeWithBatchModeAsync(code As XElement, expectedCode As XElement, testOperation As Action(Of EnvDTE.FileCodeModel, Boolean), batch As Boolean) As Task
Roslyn.Test.Utilities.WpfTestCase.RequireWpfFact($"Test calls TestAddAttributeWithBatchModeAsync which means we're creating new CodeModel elements.")
WpfTestRunner.RequireWpfFact($"Test calls {NameOf(Me.TestAddAttributeWithBatchModeAsync)} which means we're creating new {NameOf(EnvDTE.CodeModel)} elements.")
' NOTE: this method is the same as MyBase.TestOperation, but tells the lambda whether we are batching or not.
' This is because the tests have different behavior up until EndBatch is called.
......@@ -942,7 +942,7 @@ End Class
Dim buffer = state.Workspace.Documents.Single().TextBuffer
buffer.Replace(New Text.Span(0, 1), "c")
WpfTestCase.RequireWpfFact("Test requires FileCodeModel.EndBatch")
WpfTestRunner.RequireWpfFact($"Test requires {NameOf(EnvDTE80.FileCodeModel2)}.{NameOf(EnvDTE80.FileCodeModel2.EndBatch)}")
fileCodeModel.EndBatch()
Assert.Contains("Class C", buffer.CurrentSnapshot.GetText(), StringComparison.Ordinal)
......
......@@ -36,7 +36,7 @@ class 123 { }
Dim buffer = workspace.Documents.First().GetTextBuffer()
WpfTestCase.RequireWpfFact("This test uses IForegroundNotificationService")
WpfTestRunner.RequireWpfFact($"This test uses {NameOf(IForegroundNotificationService)}")
Dim foregroundService = workspace.GetService(Of IForegroundNotificationService)()
Dim provider = New DiagnosticsSquiggleTaggerProvider(diagnosticService, foregroundService, listenerProvider)
Dim tagger = provider.CreateTagger(Of IErrorTag)(buffer)
......
......@@ -30,7 +30,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.EditAndContinue
Assert.Equal(Of UInteger)(0, mockVsBuffer._oldFlags)
Dim mockEditorAdaptersFactoryService = New VsEditorAdaptersFactoryServiceMock(mockVsBuffer)
WpfTestCase.RequireWpfFact($"{NameOf(VsReadOnlyDocumentTracker)} is thread affinitized")
WpfTestRunner.RequireWpfFact($"{NameOf(VsReadOnlyDocumentTracker)} is thread affinitized")
Dim readOnlyDocumentTracker As VsReadOnlyDocumentTracker
Dim sessionReason As SessionReadOnlyReason
......
......@@ -8,6 +8,7 @@ Imports Microsoft.CodeAnalysis.Editor.UnitTests.Utilities.GoToHelpers
Imports Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces
Imports Microsoft.CodeAnalysis.Test.Utilities
Imports Roslyn.Test.Utilities
Imports Roslyn.Utilities
Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.GoToDefinition
<[UseExportProvider]>
......@@ -38,7 +39,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.GoToDefinition
Dim presenter = New MockStreamingFindUsagesPresenter(Sub() Exit Sub)
WpfTestCase.RequireWpfFact($"{NameOf(GoToDefinitionHelpers)}.{NameOf(GoToDefinitionHelpers.TryGoToDefinition)} assumes it's on the UI thread with a WaitAndGetResult call")
WpfTestRunner.RequireWpfFact($"{NameOf(GoToDefinitionHelpers)}.{NameOf(GoToDefinitionHelpers.TryGoToDefinition)} assumes it's on the UI thread with a {NameOf(TaskExtensions.WaitAndGetResult)} call")
Dim success = GoToDefinitionHelpers.TryGoToDefinition(
symbolInfo.Symbol, document.Project,
{New Lazy(Of IStreamingFindUsagesPresenter)(Function() presenter)},
......
......@@ -144,7 +144,7 @@ Class C
workspace.CurrentSolution,
componentModel)
WpfTestCase.RequireWpfFact("Test explicitly creates an IWpfTextView")
WpfTestRunner.RequireWpfFact($"Test explicitly creates an {NameOf(IWpfTextView)}")
Dim textEditorFactory = componentModel.GetService(Of ITextEditorFactoryService)
Using disposableView As DisposableTextView = textEditorFactory.CreateDisposableTextView()
previewEngine.SetTextView(disposableView.TextView)
......@@ -210,7 +210,7 @@ Class C
workspace.CurrentSolution,
componentModel)
WpfTestCase.RequireWpfFact("Test explicitly creates an IWpfTextView")
WpfTestRunner.RequireWpfFact($"Test explicitly creates an {NameOf(IWpfTextView)}")
Dim textEditorFactory = componentModel.GetService(Of ITextEditorFactoryService)
Using disposableView As DisposableTextView = textEditorFactory.CreateDisposableTextView()
previewEngine.SetTextView(disposableView.TextView)
......
......@@ -14,6 +14,7 @@ Imports Microsoft.VisualStudio.LanguageServices.Implementation.CodeModel.Interop
Imports Microsoft.VisualStudio.LanguageServices.Implementation.Interop
Imports Microsoft.VisualStudio.LanguageServices.UnitTests.CodeModel.Mocks
Imports Microsoft.VisualStudio.Shell.Interop
Imports Roslyn.Test.Utilities
Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.CodeModel
Friend Module CodeModelTestHelpers
......@@ -143,7 +144,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.CodeModel
Dim result As EnvDTE.CodeElement = Nothing
For Each candidateScope In candidateScopes
Roslyn.Test.Utilities.WpfTestCase.RequireWpfFact($"{NameOf(GetCodeElementAtCursor)} creates CodeElements and thus uses the affinited CleanableWeakComHandleTable")
WpfTestRunner.RequireWpfFact($"{NameOf(GetCodeElementAtCursor)} creates {NameOf(EnvDTE.CodeElement)}s and thus uses the affinited {NameOf(CleanableWeakComHandleTable(Of SyntaxNodeKey, EnvDTE.CodeElement))}")
Try
result = state.FileCodeModelObject.CodeElementFromPosition(cursorPosition, candidateScope)
......
......@@ -14,6 +14,26 @@ public static class TaskJoinExtensions
/// during the join operation.
/// </summary>
public static void JoinUsingDispatcher(this Task task, CancellationToken cancellationToken)
{
JoinUsingDispatcherNoResult(task, cancellationToken);
// Handle task completion by throwing the appropriate exception on failure
task.GetAwaiter().GetResult();
}
/// <summary>
/// Joins a <see cref="Task{TResult}"/> to the current thread with a <see cref="Dispatcher"/> message pump in
/// place during the join operation.
/// </summary>
public static TResult JoinUsingDispatcher<TResult>(this Task<TResult> task, CancellationToken cancellationToken)
{
JoinUsingDispatcherNoResult(task, cancellationToken);
// Handle task completion by throwing the appropriate exception on failure
return task.GetAwaiter().GetResult();
}
private static void JoinUsingDispatcherNoResult(Task task, CancellationToken cancellationToken)
{
var frame = new DispatcherFrame();
......@@ -35,9 +55,6 @@ public static void JoinUsingDispatcher(this Task task, CancellationToken cancell
Assert.True(cancellationToken.IsCancellationRequested);
cancellationToken.ThrowIfCancellationRequested();
}
// Handle task completion by throwing the appropriate exception on failure
task.GetAwaiter().GetResult();
}
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册