-
Notifications
You must be signed in to change notification settings - Fork 33
Add cocoa fact attribute and related unit tests #68
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 9 commits
b026c64
d7718fa
9516e6d
8fbeb9a
e996a5c
897247b
53bf326
fca1e86
0d636e2
68e2e49
04de106
0bdc567
2ca5096
b621858
0695648
77e6854
a7cc92c
5727671
3cf676c
b13aa1d
153f2ec
cf8b3a6
565faf6
13063f2
c2531a3
3d32350
d03833a
3871190
715bbfc
4eb7a71
bdb092f
f4f07b8
f081705
d5f36c7
8110c72
8a3a3b6
8e75d0c
30036b3
696daac
bb80ec5
09fe7b4
1de23a6
643e8bf
7c62d1b
676ed17
9f0cb71
90dae75
05460db
36b599c
6dcf89f
30d506a
41570f8
2a198ad
67c4d92
621aeb1
b99bc9a
aec7cf0
2425934
3c99e99
95c3b1f
a44b4c6
b832272
e6db7d5
37e2b59
ef65384
72516ac
d9d14bc
098c9f1
3efebf8
89a130d
d028403
b1a399d
7cf2afb
20e3476
7f8b789
7ae1504
52180e4
9dbf6be
1b9a72d
aaf5ead
c6e76e7
d69935f
1321b3c
b62abc3
892ca8b
bc5256d
8f61172
4400fed
795fe77
f9e66fc
6a47cdd
c04316a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,6 @@ | ||
| { | ||
| "sdk": { | ||
| "version": "7.0.101", | ||
| "version": "7.0.100", | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I presume this is just to allow you to build locally on your box and not because 7.0.101 is broken somehow. We'll need to revert this before merging. You could do it now and upgrade your SDK to solve the problem if you want.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yup, this whole branch was just us getting far enough locally to have something we could mess with in the Mac editor project. It's definitely not cleaned up or complete yet. 😄 |
||
| "rollForward": "patch", | ||
| "allowPrerelease": false | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| // Copyright (c) Andrew Arnott. All rights reserved. | ||
| // Licensed under the Ms-PL license. See LICENSE.txt file in the project root for full license information. | ||
| using System; | ||
| using System.Threading; | ||
| using Xunit.Sdk; | ||
|
|
||
| namespace Xunit | ||
| { | ||
| /// <summary> | ||
| /// Identifies an xunit test that starts on with a <see cref="System.Threading.SynchronizationContext"/> | ||
| /// running on <see cref="Foundation.NSRunLoop.Main"/>. | ||
| /// </summary> | ||
| [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] | ||
| [XunitTestCaseDiscoverer("Xunit.Sdk.CocoaFactDiscoverer", ThisAssembly.AssemblyName)] | ||
| public class CocoaFactAttribute : FactAttribute | ||
| { | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| // Copyright (c) Andrew Arnott. All rights reserved. | ||
| // Licensed under the Ms-PL license. See LICENSE.txt file in the project root for full license information. | ||
| using System; | ||
| using System.Threading; | ||
| using Xunit.Sdk; | ||
|
|
||
| namespace Xunit | ||
| { | ||
| /// <summary> | ||
| /// Identifies an xunit theory that starts on with a <see cref="System.Threading.SynchronizationContext"/> | ||
| /// running on <see cref="Foundation.NSRunLoop.Main"/> | ||
| /// </summary> | ||
| [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] | ||
| [XunitTestCaseDiscoverer("Xunit.Sdk.CocoaTheoryDiscoverer", ThisAssembly.AssemblyName)] | ||
| public class CocoaTheoryAttribute : TheoryAttribute | ||
| { | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| // Copyright (c) Andrew Arnott. All rights reserved. | ||
| // Licensed under the Ms-PL license. See LICENSE.txt file in the project root for full license information. | ||
|
|
||
| using System; | ||
| using System.Linq; | ||
| using System.Runtime.CompilerServices; | ||
| using System.Runtime.InteropServices; | ||
| using Xunit.Abstractions; | ||
|
|
||
| namespace Xunit.Sdk | ||
| { | ||
| /// <summary> | ||
| /// The discovery class for <see cref="CocoaFactDiscoverer"/>. | ||
| /// </summary> | ||
| public class CocoaFactDiscoverer : FactDiscoverer | ||
| { | ||
| private readonly IMessageSink diagnosticMessageSink; | ||
|
|
||
| /// <summary> | ||
| /// Initializes a new instance of the <see cref="CocoaFactDiscoverer"/> class. | ||
| /// </summary> | ||
| /// <param name="diagnosticMessageSink">The diagnostic message sink.</param> | ||
| public CocoaFactDiscoverer(IMessageSink diagnosticMessageSink) | ||
| : base(diagnosticMessageSink) | ||
| { | ||
| this.diagnosticMessageSink = diagnosticMessageSink; | ||
| } | ||
|
|
||
| class MyMessage: IMessageSinkMessage | ||
| { | ||
| public string Message { get; set; } | ||
| } | ||
|
|
||
| protected override IXunitTestCase CreateTestCase(ITestFrameworkDiscoveryOptions discoveryOptions, ITestMethod testMethod, IAttributeInfo factAttribute) | ||
| { | ||
| var message = new MyMessage | ||
| { | ||
| Message = "Hello" | ||
| }; | ||
|
|
||
| Console.WriteLine("hello!!"); | ||
| diagnosticMessageSink.OnMessage(message); | ||
| if (testMethod.Method.ReturnType.Name == "System.Void" && | ||
| testMethod.Method.GetCustomAttributes(typeof(AsyncStateMachineAttribute)).Any()) | ||
| { | ||
| return new ExecutionErrorTestCase(this.diagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), TestMethodDisplayOptions.None, testMethod, "Async void methods are not supported."); | ||
| } | ||
|
|
||
| return (IXunitTestCase)new UITestCase(UITestCase.SyncContextType.Cocoa, this.diagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), testMethod); | ||
|
|
||
| //return RuntimeInformation.IsOSPlatform(OSPlatform.OSX) | ||
| // ? (IXunitTestCase)new UITestCase(UITestCase.SyncContextType.Cocoa, this.diagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), testMethod) | ||
| // : new XunitSkippedDataRowTestCase(this.diagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), discoveryOptions.MethodDisplayOptionsOrDefault(), testMethod, "Cocoa only exists on macOS."); | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,198 @@ | ||
| // Copyright (c) Aaron Bockover. All rights reserved. | ||
| // Licensed under the Ms-PL license. See LICENSE.txt file in the project root for full license information. | ||
|
|
||
| using System.Threading; | ||
|
|
||
| using Foundation; | ||
|
|
||
| namespace Xunit.Sdk | ||
| { | ||
| internal sealed class CocoaSynchronizationContext : SynchronizationContext | ||
| { | ||
|
|
||
| private readonly Queue<KeyValuePair<SendOrPostCallback, object?>> messageQueue = new Queue<KeyValuePair<SendOrPostCallback, object?>>(); | ||
| private readonly int mainThread = Environment.CurrentManagedThreadId; | ||
| private readonly AsyncAutoResetEvent workItemDone = new AsyncAutoResetEvent(); | ||
| private readonly string name; | ||
| private readonly bool shouldSetAsCurrent; | ||
| private int activeOperations; | ||
| private bool pumping; | ||
| private bool pumpingEnded; | ||
| private ExceptionAggregator? aggregator; | ||
|
|
||
| /// <summary> | ||
| /// Initializes a new instance of the <see cref="CocoaSynchronizationContext"/> class. | ||
| /// </summary> | ||
| public CocoaSynchronizationContext(string name, bool shouldSetAsCurrent) | ||
| { | ||
| this.name = name; | ||
| this.shouldSetAsCurrent = shouldSetAsCurrent; | ||
| } | ||
|
|
||
| internal bool IsInContext => this.mainThread == Environment.CurrentManagedThreadId; | ||
|
|
||
| private bool AnyMessagesInQueue | ||
| { | ||
| get | ||
| { | ||
| lock (this.messageQueue) | ||
| { | ||
| return this.messageQueue.Count > 0; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| private bool AnyPendingOperations => Volatile.Read(ref this.activeOperations) > 0; | ||
|
|
||
| public override SynchronizationContext CreateCopy() | ||
| => new CocoaSynchronizationContext(name, shouldSetAsCurrent); | ||
|
|
||
| public override void Post(SendOrPostCallback d, object state) | ||
| => NSRunLoop.Main.BeginInvokeOnMainThread(() => d(state)); | ||
|
|
||
| public override void Send(SendOrPostCallback d, object state) | ||
| => NSRunLoop.Main.InvokeOnMainThread(() => d(state)); | ||
|
|
||
| /// <summary> | ||
| /// Blocks the calling thread to pump messages until a task has completed. | ||
| /// </summary> | ||
| /// <param name="untilCompleted">The task that must complete to break out of the message loop.</param> | ||
| public void PumpMessages(Task untilCompleted) | ||
| { | ||
| this.VerifyState(); | ||
|
|
||
| this.pumping = true; | ||
| try | ||
| { | ||
| // Arrange to wake up immediately when the task completes. | ||
| untilCompleted.ContinueWith( | ||
| _ => | ||
| { | ||
| lock (this.messageQueue) | ||
| { | ||
| Monitor.Pulse(this.messageQueue); | ||
| } | ||
| }, | ||
| TaskScheduler.Default); | ||
|
|
||
| // Now run the message loop until the task completes. | ||
| while (!untilCompleted.IsCompleted) | ||
| { | ||
| this.TryOneWorkItem(); | ||
| } | ||
| } | ||
| finally | ||
| { | ||
| this.pumping = false; | ||
| this.pumpingEnded = true; | ||
| } | ||
| } | ||
|
|
||
| public async Task WaitForOperationCompletionAsync() | ||
| { | ||
| while (this.AnyPendingOperations || this.AnyMessagesInQueue) | ||
| { | ||
| await this.workItemDone.WaitAsync().ConfigureAwait(false); | ||
| } | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Pump messages until all pending operations have completed | ||
| /// and the message queue is empty. | ||
| /// </summary> | ||
| public void CompleteOperations() | ||
| { | ||
| this.VerifyState(); | ||
| this.pumping = true; | ||
| try | ||
| { | ||
| while (this.AnyPendingOperations || this.AnyMessagesInQueue) | ||
| { | ||
| this.TryOneWorkItem(); | ||
| } | ||
| } | ||
| finally | ||
| { | ||
| this.pumping = false; | ||
| } | ||
| } | ||
|
|
||
| /// <inheritdoc /> | ||
| public override void OperationStarted() | ||
| { | ||
| Interlocked.Increment(ref this.activeOperations); | ||
| } | ||
|
|
||
| /// <inheritdoc /> | ||
| public override void OperationCompleted() | ||
| { | ||
| int result = Interlocked.Decrement(ref this.activeOperations); | ||
| if (result == 0) | ||
| { | ||
| // Give any message waiter a heads up that the operation count has reached zero, | ||
| // in case the queue is empty at the same time the operation count is, which | ||
| // is usually a sign to return to its caller. | ||
| lock (this.messageQueue) | ||
| { | ||
| Monitor.Pulse(this.messageQueue); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| internal void SetExceptionAggregator(ExceptionAggregator aggregator) | ||
| { | ||
| this.aggregator = aggregator; | ||
| } | ||
|
|
||
| private void VerifyState() | ||
| { | ||
| if (Environment.CurrentManagedThreadId != this.mainThread) | ||
| { | ||
| throw new InvalidOperationException("Wrong thread"); | ||
| } | ||
|
|
||
| if (Current != this && this.shouldSetAsCurrent) | ||
| { | ||
| throw new InvalidOperationException("Wrong sync context"); | ||
| } | ||
|
|
||
| if (this.pumping) | ||
| { | ||
| throw new InvalidOperationException("Already pumping"); | ||
| } | ||
| } | ||
|
|
||
| private bool TryOneWorkItem() | ||
| { | ||
| KeyValuePair<SendOrPostCallback, object?> work = default; | ||
| lock (this.messageQueue) | ||
| { | ||
| if (this.messageQueue.Count == 0) | ||
| { | ||
| Monitor.Wait(this.messageQueue); | ||
| return false; | ||
| } | ||
|
|
||
| work = this.messageQueue.Dequeue(); | ||
| } | ||
|
|
||
| try | ||
| { | ||
| if (this.aggregator is object) | ||
| { | ||
| this.aggregator.Run(() => work.Key(work.Value)); | ||
| } | ||
| else | ||
| { | ||
| work.Key(work.Value); | ||
| } | ||
|
|
||
| return true; | ||
| } | ||
| finally | ||
| { | ||
| this.workItemDone.Set(); | ||
| } | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| // Copyright (c) Aaron Bockover. All rights reserved. | ||
| // Licensed under the Ms-PL license. See LICENSE.txt file in the project root for full license information. | ||
|
|
||
| using System; | ||
| using System.Threading; | ||
| using System.Threading.Tasks; | ||
|
|
||
| using Foundation; | ||
|
|
||
| namespace Xunit.Sdk | ||
| { | ||
| internal class CocoaSynchronizationContextAdapter : SyncContextAdapter | ||
| { | ||
| internal static readonly SyncContextAdapter Default = new CocoaSynchronizationContextAdapter(); | ||
|
|
||
| private CocoaSynchronizationContextAdapter() | ||
| { | ||
| } | ||
|
|
||
| internal override bool CanCompleteOperations => true; | ||
|
|
||
| internal override SynchronizationContext Create(string name) => new CocoaSynchronizationContext(name, this.ShouldSetAsCurrent); | ||
|
|
||
| internal override Task WaitForOperationCompletionAsync(SynchronizationContext syncContext) => ((CocoaSynchronizationContext)syncContext).WaitForOperationCompletionAsync(); | ||
|
|
||
| // internal override void CompleteOperations() | ||
| // { | ||
| // } | ||
|
|
||
| internal override void PumpTill(SynchronizationContext synchronizationContext, Task task) | ||
| { | ||
| ((CocoaSynchronizationContext)synchronizationContext).PumpMessages(task); | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| // Copyright (c) Andrew Arnott. All rights reserved. | ||
| // Licensed under the Ms-PL license. See LICENSE.txt file in the project root for full license information. | ||
|
|
||
| using System.Collections.Generic; | ||
| using System.Runtime.InteropServices; | ||
| using Xunit.Abstractions; | ||
|
|
||
| namespace Xunit.Sdk | ||
| { | ||
| /// <summary> | ||
| /// The discovery class for <see cref="CocoaTheoryAttribute"/>. | ||
| /// </summary> | ||
| public class CocoaTheoryDiscoverer : TheoryDiscoverer | ||
| { | ||
| /// <summary> | ||
| /// Initializes a new instance of the <see cref="CocoaTheoryDiscoverer"/> class. | ||
| /// </summary> | ||
| /// <param name="diagnosticMessageSink">The diagnostic message sink.</param> | ||
| public CocoaTheoryDiscoverer(IMessageSink diagnosticMessageSink) | ||
| : base(diagnosticMessageSink) | ||
| { | ||
| } | ||
|
|
||
| protected override IEnumerable<IXunitTestCase> CreateTestCasesForDataRow(ITestFrameworkDiscoveryOptions discoveryOptions, ITestMethod testMethod, IAttributeInfo theoryAttribute, object[] dataRow) | ||
| { | ||
| yield return (IXunitTestCase)new UITestCase(UITestCase.SyncContextType.Cocoa, this.DiagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), testMethod, dataRow); | ||
| //yield return RuntimeInformation.IsOSPlatform(OSPlatform.OSX) | ||
| // ? (IXunitTestCase)new UITestCase(UITestCase.SyncContextType.Cocoa, this.DiagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), testMethod, dataRow) | ||
| // : new XunitSkippedDataRowTestCase(this.DiagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), discoveryOptions.MethodDisplayOptionsOrDefault(), testMethod, "Cocoa only exists on macOS."); | ||
| } | ||
|
|
||
| protected override IEnumerable<IXunitTestCase> CreateTestCasesForTheory(ITestFrameworkDiscoveryOptions discoveryOptions, ITestMethod testMethod, IAttributeInfo theoryAttribute) | ||
| { | ||
| yield return (IXunitTestCase)new UITheoryTestCase(UITestCase.SyncContextType.Cocoa, this.DiagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), TestMethodDisplayOptions.None, testMethod); | ||
| //yield return RuntimeInformation.IsOSPlatform(OSPlatform.OSX) | ||
| // ? (IXunitTestCase)new UITheoryTestCase(UITestCase.SyncContextType.Cocoa, this.DiagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), TestMethodDisplayOptions.None, testMethod) | ||
| // : new XunitSkippedDataRowTestCase(this.DiagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), discoveryOptions.MethodDisplayOptionsOrDefault(), testMethod, "Cocoa only exists on macOS."); | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is a contains syntax that may be better to use here if you're liable to use
-macos10.15, so that the condition doesn't silently start failing when you later move to-macos12.I think it's roughly this:
$(TargetFramework.Contains('macos'))