Skip to content

Commit 9e61524

Browse files
authored
Cleaned up the code to remove the synchronization context (#78)
1 parent 389a96b commit 9e61524

6 files changed

Lines changed: 167 additions & 166 deletions

File tree

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
using System;
2+
using System.Threading;
3+
4+
namespace Chill.Common
5+
{
6+
internal static class NoSynchronizationContextScope
7+
{
8+
public static DisposingAction Enter()
9+
{
10+
var context = SynchronizationContext.Current;
11+
SynchronizationContext.SetSynchronizationContext(null);
12+
return new DisposingAction(() => SynchronizationContext.SetSynchronizationContext(context));
13+
}
14+
15+
internal class DisposingAction : IDisposable
16+
{
17+
private readonly Action action;
18+
19+
public DisposingAction(Action action)
20+
{
21+
this.action = action;
22+
}
23+
24+
public void Dispose()
25+
{
26+
action();
27+
}
28+
}
29+
}
30+
}

Src/Chill/Common/TaskExtensions.cs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
using System;
2+
using System.Threading.Tasks;
3+
4+
namespace Chill.Common
5+
{
6+
/// <summary>
7+
/// Some unit test frameworks (like xUnit) have their own synchronization context
8+
/// that does not work well with blocking waits and can lead to deadlocks.
9+
/// These methods create the task in the default synchronization context
10+
/// and blocks until the task is completed.
11+
/// </summary>
12+
internal static class TaskExtensions
13+
{
14+
public static void ExecuteInDefaultSynchronizationContext(this Action action)
15+
{
16+
using (NoSynchronizationContextScope.Enter())
17+
{
18+
action();
19+
}
20+
}
21+
22+
public static TResult ExecuteInDefaultSynchronizationContext<TResult>(this Func<TResult> action)
23+
{
24+
using (NoSynchronizationContextScope.Enter())
25+
{
26+
return action();
27+
}
28+
}
29+
30+
}
31+
}

Src/Chill/GivenSubject.cs

Lines changed: 52 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.Threading.Tasks;
3+
using Chill.Common;
34

45
namespace Chill
56
{
@@ -48,6 +49,24 @@ protected void WhenLater(Func<TResult> whenFunc)
4849
When(whenFunc, deferredExecution: true);
4950
}
5051

52+
/// <summary>
53+
/// Records the asynchronous action that will trigger the actual test, when later executing the <see cref="WhenAction"/>
54+
/// </summary>
55+
protected void WhenLater(Func<Task<TResult>> whenFunc)
56+
{
57+
When(whenFunc, deferredExecution: true);
58+
}
59+
60+
/// <summary>
61+
/// Records the asynchronous action that will trigger the actual test
62+
/// </summary>
63+
/// <param name="whenFunc"></param>
64+
/// <param name="deferredExecution">Should the test be executed immediately or be deferred?</param>
65+
protected void When(Func<Task<TResult>> whenFunc, bool? deferredExecution = null)
66+
{
67+
When(() => whenFunc().GetAwaiter().GetResult(), deferredExecution);
68+
}
69+
5170
/// <summary>
5271
/// Records the action that will trigger the actual test
5372
/// </summary>
@@ -61,34 +80,26 @@ protected void When(Func<TResult> whenFunc, bool? deferredExecution = null)
6180
{
6281
throw new InvalidOperationException("When already defined");
6382
}
64-
whenAction = whenFunc;
83+
84+
whenAction = whenFunc.ExecuteInDefaultSynchronizationContext;
6585
if (!DeferredExecution)
6686
{
6787
EnsureTestTriggered(false);
6888
}
6989
}
7090

71-
/// <summary>
72-
/// Records the asynchronous action that will trigger the actual test, when later executing the <see cref="WhenAction"/>
73-
/// </summary>
74-
protected void WhenLater(Func<Task<TResult>> whenFunc)
91+
internal override void TriggerTest(bool expectExceptions)
7592
{
76-
When(whenFunc, deferredExecution: true);
93+
TriggerTest(() => result = whenAction(), expectExceptions);
7794
}
7895

7996
/// <summary>
80-
/// Records the asynchronous action that will trigger the actual test
97+
/// Records an asynchronous precondittion
8198
/// </summary>
82-
/// <param name="whenFunc"></param>
83-
/// <param name="deferredExecution">Should the test be executed immediately or be deferred?</param>
84-
protected void When(Func<Task<TResult>> whenFunc, bool? deferredExecution = null)
85-
{
86-
this.When(() => whenFunc.ExecuteInDefaultSynchronizationContext(), deferredExecution);
87-
}
88-
89-
internal override void TriggerTest(bool expectExceptions)
99+
/// <param name="givenFuncASync">The async precondition.</param>
100+
public void Given(Func<Task> givenFuncASync)
90101
{
91-
TriggerTest(() => result = whenAction(), expectExceptions);
102+
Given(() => givenFuncASync().GetAwaiter().GetResult());
92103
}
93104

94105
/// <summary>
@@ -98,18 +109,8 @@ internal override void TriggerTest(bool expectExceptions)
98109
public void Given(Action a)
99110
{
100111
EnsureContainer();
101-
a();
102-
}
103-
104-
/// <summary>
105-
/// Records an asynchronous precondittion
106-
/// </summary>
107-
/// <param name="givenFuncASync">The async precondition.</param>
108-
public void Given(Func<Task> givenFuncASync)
109-
{
110-
this.Given(() => givenFuncASync.ExecuteInDefaultSynchronizationContext());
112+
a.ExecuteInDefaultSynchronizationContext();
111113
}
112-
113114
}
114115

115116
/// <summary>
@@ -126,7 +127,7 @@ public abstract class GivenSubject<TSubject> : TestFor<TSubject> where TSubject
126127
/// </summary>
127128
public Action WhenAction
128129
{
129-
get { return whenAction; }
130+
get => whenAction;
130131
set
131132
{
132133
EnsureSubject();
@@ -142,6 +143,24 @@ public void WhenLater(Action whenAction)
142143
When(whenAction, deferredExecution: true);
143144
}
144145

146+
/// <summary>
147+
/// Records the asynchronous action that will trigger the actual test, when later executing the <see cref="WhenAction"/>
148+
/// </summary>
149+
public void WhenLater(Func<Task> whenActionAsync)
150+
{
151+
When(whenActionAsync, deferredExecution: true);
152+
}
153+
154+
/// <summary>
155+
/// Records the asynchronous action that will trigger the actual test
156+
/// </summary>
157+
/// <param name="whenActionAsync"></param>
158+
/// <param name="deferredExecution">Should the test be executed immediately or be deferred?</param>
159+
public void When(Func<Task> whenActionAsync, bool? deferredExecution = null)
160+
{
161+
When(() => whenActionAsync().GetAwaiter().GetResult(), deferredExecution);
162+
}
163+
145164
/// <summary>
146165
/// Records the action that will trigger the actual test
147166
/// </summary>
@@ -155,32 +174,14 @@ public void When(Action whenAction, bool? deferredExecution = null)
155174
{
156175
throw new InvalidOperationException("When already defined");
157176
}
158-
this.whenAction = whenAction;
177+
this.whenAction = whenAction.ExecuteInDefaultSynchronizationContext;
159178
if (!DeferredExecution)
160179
{
161180
EnsureTestTriggered(false);
162181
}
163182

164183
}
165184

166-
/// <summary>
167-
/// Records the asynchronous action that will trigger the actual test, when later executing the <see cref="WhenAction"/>
168-
/// </summary>
169-
public void WhenLater(Func<Task> whenActionAsync)
170-
{
171-
When(whenActionAsync, deferredExecution: true);
172-
}
173-
174-
/// <summary>
175-
/// Records the asynchronous action that will trigger the actual test
176-
/// </summary>
177-
/// <param name="whenActionAsync"></param>
178-
/// <param name="deferredExecution">Should the test be executed immediately or be deferred?</param>
179-
public void When(Func<Task> whenActionAsync, bool? deferredExecution = null)
180-
{
181-
When(() => whenActionAsync.ExecuteInDefaultSynchronizationContext(), deferredExecution);
182-
}
183-
184185
internal override void TriggerTest(bool expectExceptions)
185186
{
186187
TriggerTest(whenAction, expectExceptions);
@@ -192,17 +193,17 @@ internal override void TriggerTest(bool expectExceptions)
192193
/// <param name="givenFuncASync">The async precondition</param>
193194
public void Given(Func<Task> givenFuncASync)
194195
{
195-
this.Given(() => givenFuncASync.ExecuteInDefaultSynchronizationContext());
196+
Given(() => givenFuncASync().GetAwaiter().GetResult());
196197
}
197198

198199
/// <summary>
199200
/// Records a precondition
200201
/// </summary>
201-
/// <param name="a"></param>
202-
public void Given(Action a)
202+
/// <param name="action"></param>
203+
public void Given(Action action)
203204
{
204205
EnsureContainer();
205-
a();
206+
action.ExecuteInDefaultSynchronizationContext();
206207
}
207208
}
208209
}

0 commit comments

Comments
 (0)