Skip to content

Commit 05628c2

Browse files
committed
Add WpfFactAttribute from Roslyn
1 parent 5644aeb commit 05628c2

15 files changed

+654
-0
lines changed
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
2+
3+
using System;
4+
5+
namespace Roslyn.Test.Utilities
6+
{
7+
public class ConditionalWpfFactAttribute : WpfFactAttribute
8+
{
9+
public ConditionalWpfFactAttribute(Type skipCondition)
10+
{
11+
var condition = Activator.CreateInstance(skipCondition) as ExecutionCondition;
12+
if (condition.ShouldSkip)
13+
{
14+
Skip = condition.SkipReason;
15+
}
16+
}
17+
}
18+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
2+
3+
using System;
4+
5+
namespace Roslyn.Test.Utilities
6+
{
7+
public class ConditionalWpfTheoryAttribute : WpfTheoryAttribute
8+
{
9+
public ConditionalWpfTheoryAttribute(Type skipCondition)
10+
{
11+
var condition = Activator.CreateInstance(skipCondition) as ExecutionCondition;
12+
if (condition.ShouldSkip)
13+
{
14+
Skip = condition.SkipReason;
15+
}
16+
}
17+
}
18+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
2+
3+
using System;
4+
using System.Threading;
5+
using System.Threading.Tasks;
6+
7+
namespace Roslyn.Utilities
8+
{
9+
internal static class SemaphoreExtensions
10+
{
11+
public static SemaphoreDisposer DisposableWait(this Semaphore semaphore, CancellationToken cancellationToken)
12+
{
13+
if (cancellationToken.CanBeCanceled)
14+
{
15+
int signalledIndex = WaitHandle.WaitAny(new[] { semaphore, cancellationToken.WaitHandle });
16+
if (signalledIndex != 0)
17+
{
18+
cancellationToken.ThrowIfCancellationRequested();
19+
throw ExceptionUtilities.Unreachable;
20+
}
21+
}
22+
else
23+
{
24+
semaphore.WaitOne();
25+
}
26+
27+
return new SemaphoreDisposer(semaphore);
28+
}
29+
30+
public static Task<SemaphoreDisposer> DisposableWaitAsync(this Semaphore semaphore, CancellationToken cancellationToken)
31+
{
32+
return Task.Factory.StartNew(
33+
() => DisposableWait(semaphore, cancellationToken),
34+
cancellationToken,
35+
TaskCreationOptions.LongRunning,
36+
TaskScheduler.Default);
37+
}
38+
39+
internal struct SemaphoreDisposer : IDisposable
40+
{
41+
private readonly Semaphore _semaphore;
42+
43+
public SemaphoreDisposer(Semaphore semaphore)
44+
{
45+
_semaphore = semaphore;
46+
}
47+
48+
public void Dispose()
49+
{
50+
_semaphore.Release();
51+
}
52+
}
53+
}
54+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
2+
3+
using System;
4+
using System.Threading;
5+
using System.Windows.Threading;
6+
7+
namespace Roslyn.Test.Utilities
8+
{
9+
public sealed class StaTaskScheduler : IDisposable
10+
{
11+
/// <summary>Gets a <see cref="StaTaskScheduler"/> for the current <see cref="AppDomain"/>.</summary>
12+
/// <remarks>We use a count of 1, because the editor ends up re-using <see cref="DispatcherObject"/>
13+
/// instances between tests, so we need to always use the same thread for our Sta tests.</remarks>
14+
public static StaTaskScheduler DefaultSta { get; } = new StaTaskScheduler();
15+
16+
/// <summary>The STA threads used by the scheduler.</summary>
17+
public Thread StaThread { get; }
18+
19+
public bool IsRunningInScheduler => StaThread.ManagedThreadId == Thread.CurrentThread.ManagedThreadId;
20+
21+
/// <summary>Initializes a new instance of the <see cref="StaTaskScheduler"/> class.</summary>
22+
public StaTaskScheduler()
23+
{
24+
using (var threadStartedEvent = new ManualResetEventSlim(initialState: false))
25+
{
26+
DispatcherSynchronizationContext synchronizationContext = null;
27+
StaThread = new Thread(() =>
28+
{
29+
var oldContext = SynchronizationContext.Current;
30+
try
31+
{
32+
// All WPF Tests need a DispatcherSynchronizationContext and we dont want to block pending keyboard
33+
// or mouse input from the user. So use background priority which is a single level below user input.
34+
synchronizationContext = new DispatcherSynchronizationContext();
35+
36+
// xUnit creates its own synchronization context and wraps any existing context so that messages are
37+
// still pumped as necessary. So we are safe setting it here, where we are not safe setting it in test.
38+
SynchronizationContext.SetSynchronizationContext(synchronizationContext);
39+
40+
threadStartedEvent.Set();
41+
42+
Dispatcher.Run();
43+
}
44+
finally
45+
{
46+
SynchronizationContext.SetSynchronizationContext(oldContext);
47+
}
48+
});
49+
StaThread.Name = $"{nameof(StaTaskScheduler)} thread";
50+
StaThread.IsBackground = true;
51+
StaThread.SetApartmentState(ApartmentState.STA);
52+
StaThread.Start();
53+
54+
threadStartedEvent.Wait();
55+
DispatcherSynchronizationContext = synchronizationContext;
56+
};
57+
}
58+
59+
public DispatcherSynchronizationContext DispatcherSynchronizationContext
60+
{
61+
get;
62+
}
63+
64+
/// <summary>
65+
/// Cleans up the scheduler by indicating that no more tasks will be queued.
66+
/// This method blocks until all threads successfully shutdown.
67+
/// </summary>
68+
public void Dispose()
69+
{
70+
if (StaThread.IsAlive)
71+
{
72+
DispatcherSynchronizationContext.Post(_ => Dispatcher.ExitAllFrames(), null);
73+
StaThread.Join();
74+
}
75+
}
76+
}
77+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
2+
3+
using System;
4+
using System.Collections.Generic;
5+
using System.Threading;
6+
using System.Threading.Tasks;
7+
8+
namespace Microsoft.CodeAnalysis.Editor.Shared.Utilities
9+
{
10+
// Based on CoreCLR's implementation of the TaskScheduler they return from TaskScheduler.FromCurrentSynchronizationContext
11+
internal class SynchronizationContextTaskScheduler : TaskScheduler
12+
{
13+
private readonly SendOrPostCallback _postCallback;
14+
private readonly SynchronizationContext _synchronizationContext;
15+
16+
internal SynchronizationContextTaskScheduler(SynchronizationContext synchronizationContext)
17+
{
18+
_postCallback = new SendOrPostCallback(PostCallback);
19+
_synchronizationContext = synchronizationContext ?? throw new ArgumentNullException(nameof(synchronizationContext));
20+
}
21+
22+
public override Int32 MaximumConcurrencyLevel => 1;
23+
24+
protected override void QueueTask(Task task)
25+
{
26+
_synchronizationContext.Post(_postCallback, task);
27+
}
28+
protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
29+
{
30+
if (SynchronizationContext.Current == _synchronizationContext)
31+
{
32+
return TryExecuteTask(task);
33+
}
34+
35+
return false;
36+
}
37+
38+
protected override IEnumerable<Task> GetScheduledTasks()
39+
{
40+
return null;
41+
}
42+
43+
private void PostCallback(object obj)
44+
{
45+
TryExecuteTask((Task)obj);
46+
}
47+
}
48+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
2+
3+
using System.Threading;
4+
using System.Threading.Tasks;
5+
using System.Windows.Threading;
6+
using Xunit;
7+
8+
namespace Roslyn.Test.Utilities
9+
{
10+
public static class TaskJoinExtensions
11+
{
12+
/// <summary>
13+
/// Joins a <see cref="Task"/> to the current thread with a <see cref="Dispatcher"/> message pump in place
14+
/// during the join operation.
15+
/// </summary>
16+
public static void JoinUsingDispatcher(this Task task, CancellationToken cancellationToken)
17+
{
18+
JoinUsingDispatcherNoResult(task, cancellationToken);
19+
20+
// Handle task completion by throwing the appropriate exception on failure
21+
task.GetAwaiter().GetResult();
22+
}
23+
24+
/// <summary>
25+
/// Joins a <see cref="Task{TResult}"/> to the current thread with a <see cref="Dispatcher"/> message pump in
26+
/// place during the join operation.
27+
/// </summary>
28+
public static TResult JoinUsingDispatcher<TResult>(this Task<TResult> task, CancellationToken cancellationToken)
29+
{
30+
JoinUsingDispatcherNoResult(task, cancellationToken);
31+
32+
// Handle task completion by throwing the appropriate exception on failure
33+
return task.GetAwaiter().GetResult();
34+
}
35+
36+
private static void JoinUsingDispatcherNoResult(Task task, CancellationToken cancellationToken)
37+
{
38+
var frame = new DispatcherFrame();
39+
40+
// When the task completes or cancellation is requested, mark the frame so we leave the message pump
41+
task.ContinueWith(
42+
t => frame.Continue = false,
43+
CancellationToken.None,
44+
TaskContinuationOptions.ExecuteSynchronously,
45+
TaskScheduler.Default);
46+
47+
using (var registration = cancellationToken.Register(() => frame.Continue = false))
48+
{
49+
Dispatcher.PushFrame(frame);
50+
}
51+
52+
// Handle cancellation by throwing an exception
53+
if (!task.IsCompleted)
54+
{
55+
Assert.True(cancellationToken.IsCancellationRequested);
56+
cancellationToken.ThrowIfCancellationRequested();
57+
}
58+
}
59+
}
60+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
2+
3+
using System;
4+
using Xunit;
5+
using Xunit.Sdk;
6+
7+
namespace Roslyn.Test.Utilities
8+
{
9+
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
10+
[XunitTestCaseDiscoverer("Roslyn.Test.Utilities.WpfFactDiscoverer", "Roslyn.Services.Test.Utilities")]
11+
public class WpfFactAttribute : FactAttribute
12+
{
13+
}
14+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
2+
3+
using System.Collections.Generic;
4+
using Xunit.Abstractions;
5+
using Xunit.Sdk;
6+
7+
namespace Roslyn.Test.Utilities
8+
{
9+
public class WpfFactDiscoverer : FactDiscoverer
10+
{
11+
private readonly IMessageSink _diagnosticMessageSink;
12+
13+
public WpfFactDiscoverer(IMessageSink diagnosticMessageSink) : base(diagnosticMessageSink)
14+
{
15+
_diagnosticMessageSink = diagnosticMessageSink;
16+
}
17+
18+
protected override IXunitTestCase CreateTestCase(ITestFrameworkDiscoveryOptions discoveryOptions, ITestMethod testMethod, IAttributeInfo factAttribute)
19+
=> new WpfTestCase(_diagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), testMethod);
20+
}
21+
22+
public class WpfTheoryDiscoverer : TheoryDiscoverer
23+
{
24+
private readonly IMessageSink _diagnosticMessageSink;
25+
26+
public WpfTheoryDiscoverer(IMessageSink diagnosticMessageSink) : base(diagnosticMessageSink)
27+
{
28+
_diagnosticMessageSink = diagnosticMessageSink;
29+
}
30+
31+
protected override IEnumerable<IXunitTestCase> CreateTestCasesForDataRow(ITestFrameworkDiscoveryOptions discoveryOptions, ITestMethod testMethod, IAttributeInfo theoryAttribute, object[] dataRow)
32+
{
33+
var testCase = new WpfTestCase(_diagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), testMethod, dataRow);
34+
return new[] { testCase };
35+
}
36+
37+
protected override IEnumerable<IXunitTestCase> CreateTestCasesForTheory(ITestFrameworkDiscoveryOptions discoveryOptions, ITestMethod testMethod, IAttributeInfo theoryAttribute)
38+
{
39+
var testCase = new WpfTheoryTestCase(_diagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), testMethod);
40+
return new[] { testCase };
41+
}
42+
}
43+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
2+
3+
using System;
4+
using System.ComponentModel;
5+
using System.Threading;
6+
using System.Threading.Tasks;
7+
using Xunit.Abstractions;
8+
using Xunit.Sdk;
9+
10+
namespace Roslyn.Test.Utilities
11+
{
12+
public sealed class WpfTestCase : XunitTestCase
13+
{
14+
public WpfTestSharedData SharedData { get; private set; }
15+
16+
[EditorBrowsable(EditorBrowsableState.Never)]
17+
[Obsolete("Called by the de-serializer; should only be called by deriving classes for de-serialization purposes")]
18+
public WpfTestCase()
19+
{
20+
}
21+
22+
public WpfTestCase(IMessageSink diagnosticMessageSink, TestMethodDisplay defaultMethodDisplay, ITestMethod testMethod, object[] testMethodArguments = null)
23+
: base(diagnosticMessageSink, defaultMethodDisplay, testMethod, testMethodArguments)
24+
{
25+
SharedData = WpfTestSharedData.Instance;
26+
}
27+
28+
public override Task<RunSummary> RunAsync(IMessageSink diagnosticMessageSink, IMessageBus messageBus, object[] constructorArguments, ExceptionAggregator aggregator, CancellationTokenSource cancellationTokenSource)
29+
{
30+
var runner = new WpfTestCaseRunner(SharedData, this, DisplayName, SkipReason, constructorArguments, TestMethodArguments, messageBus, aggregator, cancellationTokenSource);
31+
return runner.RunAsync();
32+
}
33+
34+
public override void Deserialize(IXunitSerializationInfo data)
35+
{
36+
base.Deserialize(data);
37+
SharedData = WpfTestSharedData.Instance;
38+
}
39+
}
40+
}

0 commit comments

Comments
 (0)