Merge pull request #1128 from ludovic-henry/pr19g-sgen-grayqueue
[mono.git] / mcs / class / corlib / Test / System.Runtime.CompilerServices / TaskAwaiterTest.cs
1 //
2 // TaskAwaiterTest.cs
3 //
4 // Authors:
5 //      Marek Safar  <marek.safar@gmail.com>
6 //
7 // Copyright (C) 2011 Xamarin, Inc (http://www.xamarin.com)
8 //
9 // Permission is hereby granted, free of charge, to any person obtaining
10 // a copy of this software and associated documentation files (the
11 // "Software"), to deal in the Software without restriction, including
12 // without limitation the rights to use, copy, modify, merge, publish,
13 // distribute, sublicense, and/or sell copies of the Software, and to
14 // permit persons to whom the Software is furnished to do so, subject to
15 // the following conditions:
16 //
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
19 //
20 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27 //
28
29 #if NET_4_5
30
31 using System;
32 using System.Threading;
33 using System.Threading.Tasks;
34 using NUnit.Framework;
35 using System.Runtime.CompilerServices;
36 using System.Collections.Generic;
37 using System.Collections;
38
39 namespace MonoTests.System.Runtime.CompilerServices
40 {
41         [TestFixture]
42         public class TaskAwaiterTest
43         {
44                 class Scheduler : TaskScheduler
45                 {
46                         string name;
47                         int ic, qc;
48
49                         public Scheduler (string name)
50                         {
51                                 this.name = name;
52                         }
53
54                         public int InlineCalls { get { return ic; } }
55                         public int QueueCalls { get { return qc; } }
56
57                         protected override IEnumerable<Task> GetScheduledTasks ()
58                         {
59                                 throw new NotImplementedException ();
60                         }
61
62                         protected override void QueueTask (Task task)
63                         {
64                                 Interlocked.Increment (ref qc);
65                                 ThreadPool.QueueUserWorkItem (o => {
66                                         TryExecuteTask (task);
67                                 });
68                         }
69
70                         protected override bool TryExecuteTaskInline (Task task, bool taskWasPreviouslyQueued)
71                         {
72                                 Interlocked.Increment (ref ic);
73                                 return false;
74                         }
75                 }
76
77                 class SingleThreadSynchronizationContext : SynchronizationContext
78                 {
79                         readonly Queue _queue = new Queue ();
80
81                         public void RunOnCurrentThread ()
82                         {
83                                 while (_queue.Count != 0) {
84                                         var workItem = (KeyValuePair<SendOrPostCallback, object>) _queue.Dequeue ();
85                                         workItem.Key (workItem.Value);
86                                 }
87                         }
88                                 
89                         public override void Post (SendOrPostCallback d, object state)
90                         {
91                                 if (d == null) {
92                                         throw new ArgumentNullException ("d");
93                                 }
94
95                                 _queue.Enqueue (new KeyValuePair<SendOrPostCallback, object> (d, state));
96                         }
97
98                         public override void Send (SendOrPostCallback d, object state)
99                         {
100                                 throw new NotSupportedException ("Synchronously sending is not supported.");
101                         }
102                 }
103
104                 string progress;
105                 SynchronizationContext sc;
106                 ManualResetEvent mre;
107
108                 [SetUp]
109                 public void Setup ()
110                 {
111                         sc = SynchronizationContext.Current;
112                 }
113
114                 [TearDown]
115                 public void TearDown ()
116                 {
117                         SynchronizationContext.SetSynchronizationContext (sc);
118                 }
119
120                 [Test]
121                 public void GetResultFaulted ()
122                 {
123                         TaskAwaiter awaiter;
124
125                         var task = new Task (() => { throw new ApplicationException (); });
126                         awaiter = task.GetAwaiter ();
127                         task.RunSynchronously (TaskScheduler.Current);
128
129
130                         Assert.IsTrue (awaiter.IsCompleted);
131
132                         try {
133                                 awaiter.GetResult ();
134                                 Assert.Fail ();
135                         } catch (ApplicationException) {
136                         }
137                 }
138
139                 [Test]
140                 public void GetResultCanceled ()
141                 {
142                         TaskAwaiter awaiter;
143
144                         var token = new CancellationToken (true);
145                         var task = new Task (() => { }, token);
146                         awaiter = task.GetAwaiter ();
147
148                         try {
149                                 awaiter.GetResult ();
150                                 Assert.Fail ();
151                         } catch (TaskCanceledException) {
152                         }
153                 }
154
155                 [Test]
156                 public void GetResultWaitOnCompletion ()
157                 {
158                         TaskAwaiter awaiter;
159                                 
160                         var task = Task.Delay (30);
161                         awaiter = task.GetAwaiter ();
162                                 
163                         awaiter.GetResult ();
164                         Assert.AreEqual (TaskStatus.RanToCompletion, task.Status);
165                 }
166
167                 [Test]
168                 public void CustomScheduler ()
169                 {
170                         // some test runners (e.g. Touch.Unit) will execute this on the main thread and that would lock them
171                         if (!Thread.CurrentThread.IsBackground)
172                                 Assert.Ignore ("Current thread is not running in the background.");
173
174                         var a = new Scheduler ("a");
175                         var b = new Scheduler ("b");
176
177                         var t = TestCS (a, b);
178                         Assert.IsTrue (t.Wait (3000), "#0");
179                         Assert.AreEqual (0, t.Result, "#1");
180                         Assert.AreEqual (0, b.InlineCalls, "#2b");
181                         Assert.AreEqual (2, a.QueueCalls, "#3a");
182                         Assert.AreEqual (1, b.QueueCalls, "#3b");
183                 }
184
185                 static async Task<int> TestCS (TaskScheduler schedulerA, TaskScheduler schedulerB)
186                 {
187                         var res = await Task.Factory.StartNew (async () => {
188                                 if (TaskScheduler.Current != schedulerA)
189                                         return 1;
190
191                                 await Task.Factory.StartNew (
192                                         () => {
193                                                 if (TaskScheduler.Current != schedulerB)
194                                                         return 2;
195
196                                                 return 0;
197                                         }, CancellationToken.None, TaskCreationOptions.None, schedulerB);
198
199                                 if (TaskScheduler.Current != schedulerA)
200                                         return 3;
201
202                                 return 0;
203                         }, CancellationToken.None, TaskCreationOptions.None, schedulerA);
204
205                         return res.Result;
206                 }
207
208                 [Test]
209                 public void FinishedTaskOnCompleted ()
210                 {
211                         var mres = new ManualResetEvent (false);
212                         var mres2 = new ManualResetEvent (false);
213
214                         var tcs = new TaskCompletionSource<object> ();
215                         tcs.SetResult (null);
216                         var task = tcs.Task;
217
218                         var awaiter = task.GetAwaiter ();
219                         Assert.IsTrue (awaiter.IsCompleted, "#1");
220
221                         awaiter.OnCompleted(() => { 
222                                 if (mres.WaitOne (1000))
223                                         mres2.Set ();
224                         });
225
226                         mres.Set ();
227                         // this will only terminate correctly if the test was not executed from the main thread
228                         // e.g. Touch.Unit defaults to run tests on the main thread and this will return false
229                         Assert.AreEqual (Thread.CurrentThread.IsBackground, mres2.WaitOne (2000), "#2");;
230                 }
231
232                 [Test]
233                 public void CompletionOnSameCustomSynchronizationContext ()
234                 {
235                         progress = "";
236                         var syncContext = new SingleThreadSynchronizationContext ();
237                         SynchronizationContext.SetSynchronizationContext (syncContext);
238
239                         syncContext.Post (delegate {
240                                 Go (syncContext);
241                         }, null);
242
243                         // Custom message loop
244                         var cts = new CancellationTokenSource ();
245                         cts.CancelAfter (5000);
246                         while (progress.Length != 3 && !cts.IsCancellationRequested) {
247                                 syncContext.RunOnCurrentThread ();
248                                 Thread.Sleep (0);
249                         }
250
251                         Assert.AreEqual ("123", progress);
252                 }
253
254                 async void Go (SynchronizationContext ctx)
255                 {
256                         await Wait (ctx);
257
258                         progress += "2";
259                 }
260
261                 async Task Wait (SynchronizationContext ctx)
262                 {
263                         await Task.Delay (10); // Force block suspend/return
264
265                         ctx.Post (l => progress += "3", null);
266
267                         progress += "1";
268
269                         // Exiting same context - no need to post continuation
270                 }
271
272                 [Test]
273                 public void CompletionOnDifferentCustomSynchronizationContext ()
274                 {
275                         mre = new ManualResetEvent (false);
276                         progress = "";
277                         var syncContext = new SingleThreadSynchronizationContext ();
278                         SynchronizationContext.SetSynchronizationContext (syncContext);
279
280                         syncContext.Post (delegate {
281                                 Go2 (syncContext);
282                         }, null);
283
284                         // Custom message loop
285                         var cts = new CancellationTokenSource ();
286                         cts.CancelAfter (5000);
287                         while (progress.Length != 3 && !cts.IsCancellationRequested) {
288                                 syncContext.RunOnCurrentThread ();
289                                 Thread.Sleep (0);
290                         }
291
292                         Assert.AreEqual ("132", progress);
293                 }
294
295                 async void Go2 (SynchronizationContext ctx)
296                 {
297                         await Wait2 (ctx);
298
299                         if (mre.WaitOne (5000))
300                                 progress += "2";
301                 }
302
303                 async Task Wait2 (SynchronizationContext ctx)
304                 {
305                         await Task.Delay (10); // Force block suspend/return
306
307                         ctx.Post (l => {
308                                 progress += "3";
309                                 mre.Set ();
310                         }, null);
311
312                         progress += "1";
313
314                         SynchronizationContext.SetSynchronizationContext (null);
315                 }
316         }
317 }
318
319 #endif