Merge pull request #1014 from txdv/master
[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
48                         public Scheduler (string name)
49                         {
50                                 this.name = name;
51                         }
52
53                         public int InlineCalls { get; set; }
54                         public int QueueCalls { get; set; }
55
56                         protected override IEnumerable<Task> GetScheduledTasks ()
57                         {
58                                 throw new NotImplementedException ();
59                         }
60
61                         protected override void QueueTask (Task task)
62                         {
63                                 ++QueueCalls;
64                                 ThreadPool.QueueUserWorkItem (o => {
65                                         TryExecuteTask (task);
66                                 });
67                         }
68
69                         protected override bool TryExecuteTaskInline (Task task, bool taskWasPreviouslyQueued)
70                         {
71                                 ++InlineCalls;
72                                 return false;
73                         }
74                 }
75
76                 class SingleThreadSynchronizationContext : SynchronizationContext
77                 {
78                         readonly Queue _queue = new Queue ();
79
80                         public void RunOnCurrentThread ()
81                         {
82                                 while (_queue.Count != 0) {
83                                         var workItem = (KeyValuePair<SendOrPostCallback, object>) _queue.Dequeue ();
84                                         workItem.Key (workItem.Value);
85                                 }
86                         }
87                                 
88                         public override void Post (SendOrPostCallback d, object state)
89                         {
90                                 if (d == null) {
91                                         throw new ArgumentNullException ("d");
92                                 }
93
94                                 _queue.Enqueue (new KeyValuePair<SendOrPostCallback, object> (d, state));
95                         }
96
97                         public override void Send (SendOrPostCallback d, object state)
98                         {
99                                 throw new NotSupportedException ("Synchronously sending is not supported.");
100                         }
101                 }
102
103                 string progress;
104                 SynchronizationContext sc;
105
106                 [SetUp]
107                 public void Setup ()
108                 {
109                         sc = SynchronizationContext.Current;
110                 }
111
112                 [TearDown]
113                 public void TearDown ()
114                 {
115                         SynchronizationContext.SetSynchronizationContext (sc);
116                 }
117
118                 [Test]
119                 public void GetResultFaulted ()
120                 {
121                         TaskAwaiter awaiter;
122
123                         var task = new Task (() => { throw new ApplicationException (); });
124                         awaiter = task.GetAwaiter ();
125                         task.RunSynchronously (TaskScheduler.Current);
126
127
128                         Assert.IsTrue (awaiter.IsCompleted);
129
130                         try {
131                                 awaiter.GetResult ();
132                                 Assert.Fail ();
133                         } catch (ApplicationException) {
134                         }
135                 }
136
137                 [Test]
138                 public void GetResultCanceled ()
139                 {
140                         TaskAwaiter awaiter;
141
142                         var token = new CancellationToken (true);
143                         var task = new Task (() => { }, token);
144                         awaiter = task.GetAwaiter ();
145
146                         try {
147                                 awaiter.GetResult ();
148                                 Assert.Fail ();
149                         } catch (TaskCanceledException) {
150                         }
151                 }
152
153                 [Test]
154                 public void GetResultWaitOnCompletion ()
155                 {
156                         TaskAwaiter awaiter;
157                                 
158                         var task = Task.Delay (30);
159                         awaiter = task.GetAwaiter ();
160                                 
161                         awaiter.GetResult ();
162                         Assert.AreEqual (TaskStatus.RanToCompletion, task.Status);
163                 }
164
165                 [Test]
166                 public void CustomScheduler ()
167                 {
168                         // some test runners (e.g. Touch.Unit) will execute this on the main thread and that would lock them
169                         if (!Thread.CurrentThread.IsBackground)
170                                 Assert.Ignore ("Current thread is not running in the background.");
171
172                         var a = new Scheduler ("a");
173                         var b = new Scheduler ("b");
174
175                         var t = TestCS (a, b);
176                         Assert.IsTrue (t.Wait (3000), "#0");
177                         Assert.AreEqual (0, t.Result, "#1");
178                         Assert.AreEqual (0, b.InlineCalls, "#2b");
179                         Assert.AreEqual (2, a.QueueCalls, "#3a");
180                         Assert.AreEqual (1, b.QueueCalls, "#3b");
181                 }
182
183                 static async Task<int> TestCS (TaskScheduler schedulerA, TaskScheduler schedulerB)
184                 {
185                         var res = await Task.Factory.StartNew (async () => {
186                                 if (TaskScheduler.Current != schedulerA)
187                                         return 1;
188
189                                 await Task.Factory.StartNew (
190                                         () => {
191                                                 if (TaskScheduler.Current != schedulerB)
192                                                         return 2;
193
194                                                 return 0;
195                                         }, CancellationToken.None, TaskCreationOptions.None, schedulerB);
196
197                                 if (TaskScheduler.Current != schedulerA)
198                                         return 3;
199
200                                 return 0;
201                         }, CancellationToken.None, TaskCreationOptions.None, schedulerA);
202
203                         return res.Result;
204                 }
205
206                 [Test]
207                 public void FinishedTaskOnCompleted ()
208                 {
209                         var mres = new ManualResetEvent (false);
210                         var mres2 = new ManualResetEvent (false);
211
212                         var tcs = new TaskCompletionSource<object> ();
213                         tcs.SetResult (null);
214                         var task = tcs.Task;
215
216                         var awaiter = task.GetAwaiter ();
217                         Assert.IsTrue (awaiter.IsCompleted, "#1");
218
219                         awaiter.OnCompleted(() => { 
220                                 if (mres.WaitOne (1000))
221                                         mres2.Set ();
222                         });
223
224                         mres.Set ();
225                         // this will only terminate correctly if the test was not executed from the main thread
226                         // e.g. Touch.Unit defaults to run tests on the main thread and this will return false
227                         Assert.AreEqual (Thread.CurrentThread.IsBackground, mres2.WaitOne (2000), "#2");;
228                 }
229
230                 [Test]
231                 public void CompletionOnSameCustomSynchronizationContext ()
232                 {
233                         progress = "";
234                         var syncContext = new SingleThreadSynchronizationContext ();
235                         SynchronizationContext.SetSynchronizationContext (syncContext);
236
237                         syncContext.Post (delegate {
238                                 Go (syncContext);
239                         }, null);
240
241                         // Custom message loop
242                         var cts = new CancellationTokenSource ();
243                         cts.CancelAfter (5000);
244                         while (progress.Length != 3 && !cts.IsCancellationRequested) {
245                                 syncContext.RunOnCurrentThread ();
246                                 Thread.Sleep (0);
247                         }
248
249                         Assert.AreEqual ("123", progress);
250                 }
251
252                 async void Go (SynchronizationContext ctx)
253                 {
254                         await Wait (ctx);
255
256                         progress += "2";
257                 }
258
259                 async Task Wait (SynchronizationContext ctx)
260                 {
261                         await Task.Delay (10); // Force block suspend/return
262
263                         ctx.Post (l => progress += "3", null);
264
265                         progress += "1";
266
267                         // Exiting same context - no need to post continuation
268                 }
269
270                 [Test]
271                 public void CompletionOnDifferentCustomSynchronizationContext ()
272                 {
273                         progress = "";
274                         var syncContext = new SingleThreadSynchronizationContext ();
275                         SynchronizationContext.SetSynchronizationContext (syncContext);
276
277                         syncContext.Post (delegate {
278                                 Go2 (syncContext);
279                         }, null);
280
281                         // Custom message loop
282                         var cts = new CancellationTokenSource ();
283                         cts.CancelAfter (5000);
284                         while (progress.Length != 3 && !cts.IsCancellationRequested) {
285                                 syncContext.RunOnCurrentThread ();
286                                 Thread.Sleep (0);
287                         }
288
289                         Assert.AreEqual ("132", progress);
290                 }
291
292                 async void Go2 (SynchronizationContext ctx)
293                 {
294                         await Wait2 (ctx);
295
296                         progress += "2";
297                 }
298
299                 async Task Wait2 (SynchronizationContext ctx)
300                 {
301                         await Task.Delay (10); // Force block suspend/return
302
303                         ctx.Post (l => progress += "3", null);
304
305                         progress += "1";
306
307                         SynchronizationContext.SetSynchronizationContext (null);
308                 }
309         }
310 }
311
312 #endif