提交 9ef183b1 编写于 作者: S Sam Harwell

Remove SynchronizationContext monitoring from WpfFactAttribute

Test timeouts and deadlocks are handled by other infrastructure in Roslyn. This
functionality from WpfFactAttribute was interfering.

Closes #26247
上级 2b23d9dd
// 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);
}
}
}
......@@ -71,7 +71,7 @@ protected override Task<decimal> InvokeTestMethodAsync(ExceptionAggregator aggre
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);
var invoker = new XunitTestInvoker(Test, MessageBus, TestClass, ConstructorArguments, TestMethod, TestMethodArguments, BeforeAfterAttributes, aggregator, CancellationTokenSource);
return invoker.RunAsync().JoinUsingDispatcher(CancellationTokenSource.Token);
}
finally
......
......@@ -2,15 +2,9 @@
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
{
......@@ -33,13 +27,10 @@ public sealed class WpfTestSharedData
/// </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)
......@@ -59,87 +50,5 @@ public void ExecutingTest(MethodInfo testMethod)
_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();
}
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册