PageRenderTime 80ms CodeModel.GetById 17ms RepoModel.GetById 1ms app.codeStats 0ms

/test/System.Web.Http.Test/Common/TaskHelpersExtensionsTest.cs

https://bitbucket.org/mdavid/aspnetwebstack
C# | 1247 lines | 868 code | 214 blank | 165 comment | 0 complexity | d446ee456dd2ca0515fdbbabbda1ed9e MD5 | raw file
  1. using Microsoft.TestCommon;
  2. using Moq;
  3. using Xunit;
  4. using Assert = Microsoft.TestCommon.AssertEx;
  5. // There are several tests which need unreachable code (return after throw) to guarantee the correct lambda signature
  6. #pragma warning disable 0162
  7. namespace System.Threading.Tasks
  8. {
  9. public class TaskHelpersExtensionsTest
  10. {
  11. // -----------------------------------------------------------------
  12. // Task.Catch(Func<Exception, Task>)
  13. [Fact, ForceGC]
  14. public Task Catch_NoInputValue_CatchesException_Handled()
  15. {
  16. // Arrange
  17. return TaskHelpers.FromError(new InvalidOperationException())
  18. // Act
  19. .Catch(info =>
  20. {
  21. Assert.NotNull(info.Exception);
  22. Assert.IsType<InvalidOperationException>(info.Exception);
  23. return info.Handled();
  24. })
  25. // Assert
  26. .ContinueWith(task =>
  27. {
  28. Assert.Equal(TaskStatus.RanToCompletion, task.Status);
  29. });
  30. }
  31. [Fact, ForceGC]
  32. public Task Catch_NoInputValue_CatchesException_Rethrow()
  33. {
  34. // Arrange
  35. return TaskHelpers.FromError(new InvalidOperationException())
  36. // Act
  37. .Catch(info =>
  38. {
  39. return info.Throw();
  40. })
  41. // Assert
  42. .ContinueWith(task =>
  43. {
  44. Assert.Equal(TaskStatus.Faulted, task.Status);
  45. Assert.IsType<InvalidOperationException>(task.Exception.GetBaseException());
  46. });
  47. }
  48. [Fact, ForceGC]
  49. public Task Catch_NoInputValue_ReturningEmptyCatchResultFromCatchIsProhibited()
  50. {
  51. // Arrange
  52. return TaskHelpers.FromError(new Exception())
  53. // Act
  54. .Catch(info =>
  55. {
  56. return new CatchInfo.CatchResult();
  57. })
  58. // Assert
  59. .ContinueWith(task =>
  60. {
  61. Assert.Equal(TaskStatus.Faulted, task.Status);
  62. Assert.IsException<InvalidOperationException>(task.Exception, "You must set the Task property of the CatchInfo returned from the TaskHelpersExtensions.Catch continuation.");
  63. });
  64. }
  65. [Fact, ForceGC, PreserveSyncContext]
  66. public Task Catch_NoInputValue_CompletedTaskOfSuccess_DoesNotRunContinuationAndDoesNotSwitchContexts()
  67. {
  68. // Arrange
  69. bool ranContinuation = false;
  70. var syncContext = new Mock<SynchronizationContext> { CallBase = true };
  71. SynchronizationContext.SetSynchronizationContext(syncContext.Object);
  72. return TaskHelpers.Completed()
  73. // Act
  74. .Catch(info =>
  75. {
  76. ranContinuation = true;
  77. return info.Handled();
  78. })
  79. // Assert
  80. .ContinueWith(task =>
  81. {
  82. Assert.False(ranContinuation);
  83. syncContext.Verify(sc => sc.Post(It.IsAny<SendOrPostCallback>(), null), Times.Never());
  84. });
  85. }
  86. [Fact, ForceGC, PreserveSyncContext]
  87. public Task Catch_NoInputValue_CompletedTaskOfCancellation_DoesNotRunContinuationAndDoesNotSwitchContexts()
  88. {
  89. // Arrange
  90. bool ranContinuation = false;
  91. var syncContext = new Mock<SynchronizationContext> { CallBase = true };
  92. SynchronizationContext.SetSynchronizationContext(syncContext.Object);
  93. return TaskHelpers.Canceled()
  94. // Act
  95. .Catch(info =>
  96. {
  97. ranContinuation = true;
  98. return info.Handled();
  99. })
  100. // Assert
  101. .ContinueWith(task =>
  102. {
  103. Assert.False(ranContinuation);
  104. syncContext.Verify(sc => sc.Post(It.IsAny<SendOrPostCallback>(), null), Times.Never());
  105. });
  106. }
  107. [Fact, ForceGC, PreserveSyncContext]
  108. public Task Catch_NoInputValue_CompletedTaskOfFault_RunsOnSameThreadAndDoesNotPostToSynchronizationContext()
  109. {
  110. // Arrange
  111. int outerThreadId = Thread.CurrentThread.ManagedThreadId;
  112. int innerThreadId = Int32.MinValue;
  113. Exception thrownException = new Exception();
  114. Exception caughtException = null;
  115. var syncContext = new Mock<SynchronizationContext> { CallBase = true };
  116. SynchronizationContext.SetSynchronizationContext(syncContext.Object);
  117. return TaskHelpers.FromError(thrownException)
  118. // Act
  119. .Catch(info =>
  120. {
  121. caughtException = info.Exception;
  122. innerThreadId = Thread.CurrentThread.ManagedThreadId;
  123. return info.Handled();
  124. })
  125. // Assert
  126. .ContinueWith(task =>
  127. {
  128. Assert.Same(thrownException, caughtException);
  129. Assert.Equal(innerThreadId, outerThreadId);
  130. syncContext.Verify(sc => sc.Post(It.IsAny<SendOrPostCallback>(), null), Times.Never());
  131. });
  132. }
  133. [Fact, ForceGC, PreserveSyncContext]
  134. public Task Catch_NoInputValue_IncompleteTaskOfSuccess_DoesNotRunContinuationAndDoesNotSwitchContexts()
  135. {
  136. // Arrange
  137. bool ranContinuation = false;
  138. var syncContext = new Mock<SynchronizationContext> { CallBase = true };
  139. SynchronizationContext.SetSynchronizationContext(syncContext.Object);
  140. Task incompleteTask = new Task(() => { });
  141. // Act
  142. Task resultTask = incompleteTask.Catch(info =>
  143. {
  144. ranContinuation = true;
  145. return info.Handled();
  146. });
  147. // Assert
  148. incompleteTask.Start();
  149. return resultTask.ContinueWith(task =>
  150. {
  151. Assert.False(ranContinuation);
  152. syncContext.Verify(sc => sc.Post(It.IsAny<SendOrPostCallback>(), null), Times.Never());
  153. });
  154. }
  155. [Fact, ForceGC, PreserveSyncContext]
  156. public Task Catch_NoInputValue_IncompleteTaskOfCancellation_DoesNotRunContinuationAndDoesNotSwitchContexts()
  157. {
  158. // Arrange
  159. bool ranContinuation = false;
  160. var syncContext = new Mock<SynchronizationContext> { CallBase = true };
  161. SynchronizationContext.SetSynchronizationContext(syncContext.Object);
  162. Task incompleteTask = new Task(() => { });
  163. Task resultTask = incompleteTask.ContinueWith(task => TaskHelpers.Canceled()).Unwrap();
  164. // Act
  165. resultTask = resultTask.Catch(info =>
  166. {
  167. ranContinuation = true;
  168. return info.Handled();
  169. });
  170. // Assert
  171. incompleteTask.Start();
  172. return resultTask.ContinueWith(task =>
  173. {
  174. Assert.False(ranContinuation);
  175. syncContext.Verify(sc => sc.Post(It.IsAny<SendOrPostCallback>(), null), Times.Never());
  176. });
  177. }
  178. [Fact, ForceGC, PreserveSyncContext]
  179. public Task Catch_NoInputValue_IncompleteTaskOfFault_RunsOnNewThreadAndPostsToSynchronizationContext()
  180. {
  181. // Arrange
  182. int outerThreadId = Thread.CurrentThread.ManagedThreadId;
  183. int innerThreadId = Int32.MinValue;
  184. Exception thrownException = new Exception();
  185. Exception caughtException = null;
  186. var syncContext = new Mock<SynchronizationContext> { CallBase = true };
  187. SynchronizationContext.SetSynchronizationContext(syncContext.Object);
  188. Task incompleteTask = new Task(() => { throw thrownException; });
  189. // Act
  190. Task resultTask = incompleteTask.Catch(info =>
  191. {
  192. caughtException = info.Exception;
  193. innerThreadId = Thread.CurrentThread.ManagedThreadId;
  194. return info.Handled();
  195. });
  196. // Assert
  197. incompleteTask.Start();
  198. return resultTask.ContinueWith(task =>
  199. {
  200. Assert.Same(thrownException, caughtException);
  201. Assert.NotEqual(innerThreadId, outerThreadId);
  202. syncContext.Verify(sc => sc.Post(It.IsAny<SendOrPostCallback>(), null), Times.Once());
  203. });
  204. }
  205. // -----------------------------------------------------------------
  206. // Task<T>.Catch(Func<Exception, Task<T>>)
  207. [Fact, ForceGC]
  208. public Task Catch_WithInputValue_CatchesException_Handled()
  209. {
  210. // Arrange
  211. return TaskHelpers.FromError<int>(new InvalidOperationException())
  212. // Act
  213. .Catch(info =>
  214. {
  215. Assert.NotNull(info.Exception);
  216. Assert.IsType<InvalidOperationException>(info.Exception);
  217. return info.Handled(42);
  218. })
  219. // Assert
  220. .ContinueWith(task =>
  221. {
  222. Assert.Equal(TaskStatus.RanToCompletion, task.Status);
  223. });
  224. }
  225. [Fact, ForceGC]
  226. public Task Catch_WithInputValue_CatchesException_Rethrow()
  227. {
  228. // Arrange
  229. return TaskHelpers.FromError<int>(new InvalidOperationException())
  230. // Act
  231. .Catch(info =>
  232. {
  233. return info.Throw();
  234. })
  235. // Assert
  236. .ContinueWith(task =>
  237. {
  238. Assert.Equal(TaskStatus.Faulted, task.Status);
  239. Assert.IsType<InvalidOperationException>(task.Exception.GetBaseException());
  240. });
  241. }
  242. [Fact, ForceGC]
  243. public Task Catch_WithInputValue_ReturningNullFromCatchIsProhibited()
  244. {
  245. // Arrange
  246. return TaskHelpers.FromError<int>(new Exception())
  247. // Act
  248. .Catch(info =>
  249. {
  250. return new CatchInfoBase<Task>.CatchResult();
  251. })
  252. // Assert
  253. .ContinueWith(task =>
  254. {
  255. Assert.Equal(TaskStatus.Faulted, task.Status);
  256. Assert.IsException<InvalidOperationException>(task.Exception, "You must set the Task property of the CatchInfo returned from the TaskHelpersExtensions.Catch continuation.");
  257. });
  258. }
  259. [Fact, ForceGC, PreserveSyncContext]
  260. public Task Catch_WithInputValue_CompletedTaskOfSuccess_DoesNotRunContinuationAndDoesNotSwitchContexts()
  261. {
  262. // Arrange
  263. bool ranContinuation = false;
  264. var syncContext = new Mock<SynchronizationContext> { CallBase = true };
  265. SynchronizationContext.SetSynchronizationContext(syncContext.Object);
  266. return TaskHelpers.FromResult(21)
  267. // Act
  268. .Catch(info =>
  269. {
  270. ranContinuation = true;
  271. return info.Handled(42);
  272. })
  273. // Assert
  274. .ContinueWith(task =>
  275. {
  276. Assert.False(ranContinuation);
  277. syncContext.Verify(sc => sc.Post(It.IsAny<SendOrPostCallback>(), null), Times.Never());
  278. });
  279. }
  280. [Fact, ForceGC, PreserveSyncContext]
  281. public Task Catch_WithInputValue_CompletedTaskOfCancellation_DoesNotRunContinuationAndDoesNotSwitchContexts()
  282. {
  283. // Arrange
  284. bool ranContinuation = false;
  285. var syncContext = new Mock<SynchronizationContext> { CallBase = true };
  286. SynchronizationContext.SetSynchronizationContext(syncContext.Object);
  287. return TaskHelpers.Canceled<int>()
  288. // Act
  289. .Catch(info =>
  290. {
  291. ranContinuation = true;
  292. return info.Handled(42);
  293. })
  294. // Assert
  295. .ContinueWith(task =>
  296. {
  297. Assert.False(ranContinuation);
  298. syncContext.Verify(sc => sc.Post(It.IsAny<SendOrPostCallback>(), null), Times.Never());
  299. });
  300. }
  301. [Fact, ForceGC, PreserveSyncContext]
  302. public Task Catch_WithInputValue_CompletedTaskOfFault_RunsOnSameThreadAndDoesNotPostToSynchronizationContext()
  303. {
  304. // Arrange
  305. int outerThreadId = Thread.CurrentThread.ManagedThreadId;
  306. int innerThreadId = Int32.MinValue;
  307. Exception thrownException = new Exception();
  308. Exception caughtException = null;
  309. var syncContext = new Mock<SynchronizationContext> { CallBase = true };
  310. SynchronizationContext.SetSynchronizationContext(syncContext.Object);
  311. return TaskHelpers.FromError<int>(thrownException)
  312. // Act
  313. .Catch(info =>
  314. {
  315. caughtException = info.Exception;
  316. innerThreadId = Thread.CurrentThread.ManagedThreadId;
  317. return info.Handled(42);
  318. })
  319. // Assert
  320. .ContinueWith(task =>
  321. {
  322. Assert.Same(thrownException, caughtException);
  323. Assert.Equal(innerThreadId, outerThreadId);
  324. syncContext.Verify(sc => sc.Post(It.IsAny<SendOrPostCallback>(), null), Times.Never());
  325. });
  326. }
  327. [Fact, ForceGC, PreserveSyncContext]
  328. public Task Catch_WithInputValue_IncompleteTaskOfSuccess_DoesNotRunContinuationAndDoesNotSwitchContexts()
  329. {
  330. // Arrange
  331. bool ranContinuation = false;
  332. var syncContext = new Mock<SynchronizationContext> { CallBase = true };
  333. SynchronizationContext.SetSynchronizationContext(syncContext.Object);
  334. Task<int> incompleteTask = new Task<int>(() => 42);
  335. // Act
  336. Task<int> resultTask = incompleteTask.Catch(info =>
  337. {
  338. ranContinuation = true;
  339. return info.Handled(42);
  340. });
  341. // Assert
  342. incompleteTask.Start();
  343. return resultTask.ContinueWith(task =>
  344. {
  345. Assert.False(ranContinuation);
  346. syncContext.Verify(sc => sc.Post(It.IsAny<SendOrPostCallback>(), null), Times.Never());
  347. });
  348. }
  349. [Fact, ForceGC, PreserveSyncContext]
  350. public Task Catch_WithInputValue_IncompleteTaskOfCancellation_DoesNotRunContinuationAndDoesNotSwitchContexts()
  351. {
  352. // Arrange
  353. bool ranContinuation = false;
  354. var syncContext = new Mock<SynchronizationContext> { CallBase = true };
  355. SynchronizationContext.SetSynchronizationContext(syncContext.Object);
  356. Task<int> incompleteTask = new Task<int>(() => 42);
  357. Task<int> resultTask = incompleteTask.ContinueWith(task => TaskHelpers.Canceled<int>()).Unwrap();
  358. // Act
  359. resultTask = resultTask.Catch(info =>
  360. {
  361. ranContinuation = true;
  362. return info.Handled(2112);
  363. });
  364. // Assert
  365. incompleteTask.Start();
  366. return resultTask.ContinueWith(task =>
  367. {
  368. Assert.False(ranContinuation);
  369. syncContext.Verify(sc => sc.Post(It.IsAny<SendOrPostCallback>(), null), Times.Never());
  370. });
  371. }
  372. [Fact, ForceGC, PreserveSyncContext]
  373. public Task Catch_WithInputValue_IncompleteTaskOfFault_RunsOnNewThreadAndPostsToSynchronizationContext()
  374. {
  375. // Arrange
  376. int outerThreadId = Thread.CurrentThread.ManagedThreadId;
  377. int innerThreadId = Int32.MinValue;
  378. Exception thrownException = new Exception();
  379. Exception caughtException = null;
  380. var syncContext = new Mock<SynchronizationContext> { CallBase = true };
  381. SynchronizationContext.SetSynchronizationContext(syncContext.Object);
  382. Task<int> incompleteTask = new Task<int>(() => { throw thrownException; });
  383. // Act
  384. Task<int> resultTask = incompleteTask.Catch(info =>
  385. {
  386. caughtException = info.Exception;
  387. innerThreadId = Thread.CurrentThread.ManagedThreadId;
  388. return info.Handled(42);
  389. });
  390. // Assert
  391. incompleteTask.Start();
  392. return resultTask.ContinueWith(task =>
  393. {
  394. Assert.Same(thrownException, caughtException);
  395. Assert.NotEqual(innerThreadId, outerThreadId);
  396. syncContext.Verify(sc => sc.Post(It.IsAny<SendOrPostCallback>(), null), Times.Once());
  397. });
  398. }
  399. // -----------------------------------------------------------------
  400. // Task.CopyResultToCompletionSource(Task)
  401. [Fact, ForceGC]
  402. public Task CopyResultToCompletionSource_NoInputValue_SuccessfulTask()
  403. {
  404. // Arrange
  405. var tcs = new TaskCompletionSource<object>();
  406. var expectedResult = new object();
  407. return TaskHelpers.Completed()
  408. // Act
  409. .CopyResultToCompletionSource(tcs, expectedResult)
  410. // Assert
  411. .ContinueWith(task =>
  412. {
  413. Assert.Equal(TaskStatus.RanToCompletion, task.Status); // Outer task always runs to completion
  414. Assert.Equal(TaskStatus.RanToCompletion, tcs.Task.Status);
  415. Assert.Same(expectedResult, tcs.Task.Result);
  416. });
  417. }
  418. [Fact, ForceGC]
  419. public Task CopyResultToCompletionSource_NoInputValue_FaultedTask()
  420. {
  421. // Arrange
  422. var tcs = new TaskCompletionSource<object>();
  423. var expectedException = new NotImplementedException();
  424. return TaskHelpers.FromError(expectedException)
  425. // Act
  426. .CopyResultToCompletionSource(tcs, completionResult: null)
  427. // Assert
  428. .ContinueWith(task =>
  429. {
  430. Assert.Equal(TaskStatus.RanToCompletion, task.Status); // Outer task always runs to completion
  431. Assert.Equal(TaskStatus.Faulted, tcs.Task.Status);
  432. Assert.Same(expectedException, tcs.Task.Exception.GetBaseException());
  433. });
  434. }
  435. [Fact, ForceGC]
  436. public Task CopyResultToCompletionSource_NoInputValue_Canceled()
  437. {
  438. // Arrange
  439. var tcs = new TaskCompletionSource<object>();
  440. return TaskHelpers.Canceled()
  441. // Act
  442. .CopyResultToCompletionSource(tcs, completionResult: null)
  443. // Assert
  444. .ContinueWith(task =>
  445. {
  446. Assert.Equal(TaskStatus.RanToCompletion, task.Status); // Outer task always runs to completion
  447. Assert.Equal(TaskStatus.Canceled, tcs.Task.Status);
  448. });
  449. }
  450. // -----------------------------------------------------------------
  451. // Task.CopyResultToCompletionSource(Task<T>)
  452. [Fact, ForceGC]
  453. public Task CopyResultToCompletionSource_WithInputValue_SuccessfulTask()
  454. {
  455. // Arrange
  456. var tcs = new TaskCompletionSource<int>();
  457. return TaskHelpers.FromResult(42)
  458. // Act
  459. .CopyResultToCompletionSource(tcs)
  460. // Assert
  461. .ContinueWith(task =>
  462. {
  463. Assert.Equal(TaskStatus.RanToCompletion, task.Status); // Outer task always runs to completion
  464. Assert.Equal(TaskStatus.RanToCompletion, tcs.Task.Status);
  465. Assert.Equal(42, tcs.Task.Result);
  466. });
  467. }
  468. [Fact, ForceGC]
  469. public Task CopyResultToCompletionSource_WithInputValue_FaultedTask()
  470. {
  471. // Arrange
  472. var tcs = new TaskCompletionSource<int>();
  473. var expectedException = new NotImplementedException();
  474. return TaskHelpers.FromError<int>(expectedException)
  475. // Act
  476. .CopyResultToCompletionSource(tcs)
  477. // Assert
  478. .ContinueWith(task =>
  479. {
  480. Assert.Equal(TaskStatus.RanToCompletion, task.Status); // Outer task always runs to completion
  481. Assert.Equal(TaskStatus.Faulted, tcs.Task.Status);
  482. Assert.Same(expectedException, tcs.Task.Exception.GetBaseException());
  483. });
  484. }
  485. [Fact, ForceGC]
  486. public Task CopyResultToCompletionSource_WithInputValue_Canceled()
  487. {
  488. // Arrange
  489. var tcs = new TaskCompletionSource<int>();
  490. return TaskHelpers.Canceled<int>()
  491. // Act
  492. .CopyResultToCompletionSource(tcs)
  493. // Assert
  494. .ContinueWith(task =>
  495. {
  496. Assert.Equal(TaskStatus.RanToCompletion, task.Status); // Outer task always runs to completion
  497. Assert.Equal(TaskStatus.Canceled, tcs.Task.Status);
  498. });
  499. }
  500. // -----------------------------------------------------------------
  501. // Task.Finally(Action)
  502. [Fact, ForceGC, PreserveSyncContext]
  503. public Task Finally_NoInputValue_CompletedTaskOfSuccess_RunsOnSameThreadAndDoesNotPostToSynchronizationContext()
  504. {
  505. // Arrange
  506. int originalThreadId = Thread.CurrentThread.ManagedThreadId;
  507. int callbackThreadId = Int32.MinValue;
  508. var syncContext = new Mock<SynchronizationContext> { CallBase = true };
  509. SynchronizationContext.SetSynchronizationContext(syncContext.Object);
  510. return TaskHelpers.Completed()
  511. // Act
  512. .Finally(() =>
  513. {
  514. callbackThreadId = Thread.CurrentThread.ManagedThreadId;
  515. })
  516. // Assert
  517. .ContinueWith(task =>
  518. {
  519. Assert.Equal(originalThreadId, callbackThreadId);
  520. syncContext.Verify(sc => sc.Post(It.IsAny<SendOrPostCallback>(), null), Times.Never());
  521. });
  522. }
  523. [Fact, ForceGC, PreserveSyncContext]
  524. public Task Finally_NoInputValue_CompletedTaskOfCancellation_RunsOnSameThreadAndDoesNotPostToSynchronizationContext()
  525. {
  526. // Arrange
  527. int originalThreadId = Thread.CurrentThread.ManagedThreadId;
  528. int callbackThreadId = Int32.MinValue;
  529. var syncContext = new Mock<SynchronizationContext> { CallBase = true };
  530. SynchronizationContext.SetSynchronizationContext(syncContext.Object);
  531. return TaskHelpers.Canceled()
  532. // Act
  533. .Finally(() =>
  534. {
  535. callbackThreadId = Thread.CurrentThread.ManagedThreadId;
  536. })
  537. // Assert
  538. .ContinueWith(task =>
  539. {
  540. Assert.Equal(originalThreadId, callbackThreadId);
  541. syncContext.Verify(sc => sc.Post(It.IsAny<SendOrPostCallback>(), null), Times.Never());
  542. });
  543. }
  544. [Fact, ForceGC, PreserveSyncContext]
  545. public Task Finally_NoInputValue_CompletedTaskOfFault_RunsOnSameThreadAndDoesNotPostToSynchronizationContext()
  546. {
  547. // Arrange
  548. int originalThreadId = Thread.CurrentThread.ManagedThreadId;
  549. int callbackThreadId = Int32.MinValue;
  550. var syncContext = new Mock<SynchronizationContext> { CallBase = true };
  551. SynchronizationContext.SetSynchronizationContext(syncContext.Object);
  552. return TaskHelpers.FromError(new InvalidOperationException())
  553. // Act
  554. .Finally(() =>
  555. {
  556. callbackThreadId = Thread.CurrentThread.ManagedThreadId;
  557. })
  558. // Assert
  559. .ContinueWith(task =>
  560. {
  561. var ex = task.Exception; // Observe the exception
  562. Assert.Equal(originalThreadId, callbackThreadId);
  563. syncContext.Verify(sc => sc.Post(It.IsAny<SendOrPostCallback>(), null), Times.Never());
  564. });
  565. }
  566. [Fact, ForceGC, PreserveSyncContext]
  567. public Task Finally_NoInputValue_IncompleteTaskOfSuccess_RunsOnNewThreadAndPostsContinuationToSynchronizationContext()
  568. {
  569. // Arrange
  570. int originalThreadId = Thread.CurrentThread.ManagedThreadId;
  571. int callbackThreadId = Int32.MinValue;
  572. var syncContext = new Mock<SynchronizationContext> { CallBase = true };
  573. SynchronizationContext.SetSynchronizationContext(syncContext.Object);
  574. Task incompleteTask = new Task(() => { });
  575. // Act
  576. Task resultTask = incompleteTask.Finally(() =>
  577. {
  578. callbackThreadId = Thread.CurrentThread.ManagedThreadId;
  579. });
  580. // Assert
  581. incompleteTask.Start();
  582. return resultTask.ContinueWith(task =>
  583. {
  584. Assert.NotEqual(originalThreadId, callbackThreadId);
  585. syncContext.Verify(sc => sc.Post(It.IsAny<SendOrPostCallback>(), null), Times.Once());
  586. });
  587. }
  588. [Fact, ForceGC, PreserveSyncContext]
  589. public Task Finally_NoInputValue_IncompleteTaskOfCancellation_RunsOnNewThreadAndPostsContinuationToSynchronizationContext()
  590. {
  591. // Arrange
  592. int originalThreadId = Thread.CurrentThread.ManagedThreadId;
  593. int callbackThreadId = Int32.MinValue;
  594. var syncContext = new Mock<SynchronizationContext> { CallBase = true };
  595. SynchronizationContext.SetSynchronizationContext(syncContext.Object);
  596. Task incompleteTask = new Task(() => { });
  597. Task resultTask = incompleteTask.ContinueWith(task => TaskHelpers.Canceled()).Unwrap();
  598. // Act
  599. resultTask = resultTask.Finally(() =>
  600. {
  601. callbackThreadId = Thread.CurrentThread.ManagedThreadId;
  602. });
  603. // Assert
  604. incompleteTask.Start();
  605. return resultTask.ContinueWith(task =>
  606. {
  607. Assert.NotEqual(originalThreadId, callbackThreadId);
  608. syncContext.Verify(sc => sc.Post(It.IsAny<SendOrPostCallback>(), null), Times.Once());
  609. });
  610. }
  611. [Fact, ForceGC, PreserveSyncContext]
  612. public Task Finally_NoInputValue_IncompleteTaskOfFault_RunsOnNewThreadAndPostsContinuationToSynchronizationContext()
  613. {
  614. // Arrange
  615. int originalThreadId = Thread.CurrentThread.ManagedThreadId;
  616. int callbackThreadId = Int32.MinValue;
  617. var syncContext = new Mock<SynchronizationContext> { CallBase = true };
  618. SynchronizationContext.SetSynchronizationContext(syncContext.Object);
  619. Task incompleteTask = new Task(() => { throw new InvalidOperationException(); });
  620. // Act
  621. Task resultTask = incompleteTask.Finally(() =>
  622. {
  623. callbackThreadId = Thread.CurrentThread.ManagedThreadId;
  624. });
  625. // Assert
  626. incompleteTask.Start();
  627. return resultTask.ContinueWith(task =>
  628. {
  629. var ex = task.Exception; // Observe the exception
  630. Assert.NotEqual(originalThreadId, callbackThreadId);
  631. syncContext.Verify(sc => sc.Post(It.IsAny<SendOrPostCallback>(), null), Times.Once());
  632. });
  633. }
  634. // -----------------------------------------------------------------
  635. // Task<T>.Finally(Action)
  636. [Fact, ForceGC, PreserveSyncContext]
  637. public Task Finally_WithInputValue_CompletedTaskOfSuccess_RunsOnSameThreadAndDoesNotPostToSynchronizationContext()
  638. {
  639. // Arrange
  640. int originalThreadId = Thread.CurrentThread.ManagedThreadId;
  641. int callbackThreadId = Int32.MinValue;
  642. var syncContext = new Mock<SynchronizationContext> { CallBase = true };
  643. SynchronizationContext.SetSynchronizationContext(syncContext.Object);
  644. return TaskHelpers.FromResult(21)
  645. // Act
  646. .Finally(() =>
  647. {
  648. callbackThreadId = Thread.CurrentThread.ManagedThreadId;
  649. })
  650. // Assert
  651. .ContinueWith(task =>
  652. {
  653. Assert.Equal(21, task.Result);
  654. Assert.Equal(originalThreadId, callbackThreadId);
  655. syncContext.Verify(sc => sc.Post(It.IsAny<SendOrPostCallback>(), null), Times.Never());
  656. });
  657. }
  658. [Fact, ForceGC, PreserveSyncContext]
  659. public Task Finally_WithInputValue_CompletedTaskOfCancellation_RunsOnSameThreadAndDoesNotPostToSynchronizationContext()
  660. {
  661. // Arrange
  662. int originalThreadId = Thread.CurrentThread.ManagedThreadId;
  663. int callbackThreadId = Int32.MinValue;
  664. var syncContext = new Mock<SynchronizationContext> { CallBase = true };
  665. SynchronizationContext.SetSynchronizationContext(syncContext.Object);
  666. return TaskHelpers.Canceled<int>()
  667. // Act
  668. .Finally(() =>
  669. {
  670. callbackThreadId = Thread.CurrentThread.ManagedThreadId;
  671. })
  672. // Assert
  673. .ContinueWith(task =>
  674. {
  675. Assert.Equal(originalThreadId, callbackThreadId);
  676. syncContext.Verify(sc => sc.Post(It.IsAny<SendOrPostCallback>(), null), Times.Never());
  677. });
  678. }
  679. [Fact, ForceGC, PreserveSyncContext]
  680. public Task Finally_WithInputValue_CompletedTaskOfFault_RunsOnSameThreadAndDoesNotPostToSynchronizationContext()
  681. {
  682. // Arrange
  683. int originalThreadId = Thread.CurrentThread.ManagedThreadId;
  684. int callbackThreadId = Int32.MinValue;
  685. var syncContext = new Mock<SynchronizationContext> { CallBase = true };
  686. SynchronizationContext.SetSynchronizationContext(syncContext.Object);
  687. return TaskHelpers.FromError<int>(new InvalidOperationException())
  688. // Act
  689. .Finally(() =>
  690. {
  691. callbackThreadId = Thread.CurrentThread.ManagedThreadId;
  692. })
  693. // Assert
  694. .ContinueWith(task =>
  695. {
  696. var ex = task.Exception; // Observe the exception
  697. Assert.Equal(originalThreadId, callbackThreadId);
  698. syncContext.Verify(sc => sc.Post(It.IsAny<SendOrPostCallback>(), null), Times.Never());
  699. });
  700. }
  701. [Fact, ForceGC, PreserveSyncContext]
  702. public Task Finally_WithInputValue_IncompleteTaskOfSuccess_RunsOnNewThreadAndPostsContinuationToSynchronizationContext()
  703. {
  704. // Arrange
  705. int originalThreadId = Thread.CurrentThread.ManagedThreadId;
  706. int callbackThreadId = Int32.MinValue;
  707. var syncContext = new Mock<SynchronizationContext> { CallBase = true };
  708. SynchronizationContext.SetSynchronizationContext(syncContext.Object);
  709. Task incompleteTask = new Task<int>(() => 21);
  710. // Act
  711. Task resultTask = incompleteTask.Finally(() =>
  712. {
  713. callbackThreadId = Thread.CurrentThread.ManagedThreadId;
  714. });
  715. // Assert
  716. incompleteTask.Start();
  717. return resultTask.ContinueWith(task =>
  718. {
  719. Assert.NotEqual(originalThreadId, callbackThreadId);
  720. syncContext.Verify(sc => sc.Post(It.IsAny<SendOrPostCallback>(), null), Times.Once());
  721. });
  722. }
  723. [Fact, ForceGC, PreserveSyncContext]
  724. public Task Finally_WithInputValue_IncompleteTaskOfCancellation_RunsOnNewThreadAndPostsContinuationToSynchronizationContext()
  725. {
  726. // Arrange
  727. int originalThreadId = Thread.CurrentThread.ManagedThreadId;
  728. int callbackThreadId = Int32.MinValue;
  729. var syncContext = new Mock<SynchronizationContext> { CallBase = true };
  730. SynchronizationContext.SetSynchronizationContext(syncContext.Object);
  731. Task<int> incompleteTask = new Task<int>(() => 42);
  732. Task resultTask = incompleteTask.ContinueWith(task => TaskHelpers.Canceled<int>()).Unwrap();
  733. // Act
  734. resultTask = resultTask.Finally(() =>
  735. {
  736. callbackThreadId = Thread.CurrentThread.ManagedThreadId;
  737. });
  738. // Assert
  739. incompleteTask.Start();
  740. return resultTask.ContinueWith(task =>
  741. {
  742. Assert.NotEqual(originalThreadId, callbackThreadId);
  743. syncContext.Verify(sc => sc.Post(It.IsAny<SendOrPostCallback>(), null), Times.Once());
  744. });
  745. }
  746. [Fact, ForceGC, PreserveSyncContext]
  747. public Task Finally_WithInputValue_IncompleteTaskOfFault_RunsOnNewThreadAndPostsContinuationToSynchronizationContext()
  748. {
  749. // Arrange
  750. int originalThreadId = Thread.CurrentThread.ManagedThreadId;
  751. int callbackThreadId = Int32.MinValue;
  752. var syncContext = new Mock<SynchronizationContext> { CallBase = true };
  753. SynchronizationContext.SetSynchronizationContext(syncContext.Object);
  754. Task<int> incompleteTask = new Task<int>(() => { throw new InvalidOperationException(); });
  755. // Act
  756. Task resultTask = incompleteTask.Finally(() =>
  757. {
  758. callbackThreadId = Thread.CurrentThread.ManagedThreadId;
  759. });
  760. // Assert
  761. incompleteTask.Start();
  762. return resultTask.ContinueWith(task =>
  763. {
  764. var ex = task.Exception; // Observe the exception
  765. Assert.NotEqual(originalThreadId, callbackThreadId);
  766. syncContext.Verify(sc => sc.Post(It.IsAny<SendOrPostCallback>(), null), Times.Once());
  767. });
  768. }
  769. // -----------------------------------------------------------------
  770. // Task Task.Then(Action)
  771. [Fact, ForceGC]
  772. public Task Then_NoInputValue_NoReturnValue_CallsContinuation()
  773. {
  774. // Arrange
  775. bool ranContinuation = false;
  776. return TaskHelpers.Completed()
  777. // Act
  778. .Then(() =>
  779. {
  780. ranContinuation = true;
  781. })
  782. // Assert
  783. .ContinueWith(task =>
  784. {
  785. Assert.Equal(TaskStatus.RanToCompletion, task.Status);
  786. Assert.True(ranContinuation);
  787. });
  788. }
  789. [Fact, ForceGC]
  790. public Task Then_NoInputValue_NoReturnValue_ThrownExceptionIsRethrowd()
  791. {
  792. // Arrange
  793. return TaskHelpers.Completed()
  794. // Act
  795. .Then(() =>
  796. {
  797. throw new NotImplementedException();
  798. })
  799. // Assert
  800. .ContinueWith(task =>
  801. {
  802. Assert.Equal(TaskStatus.Faulted, task.Status);
  803. var ex = Assert.Single(task.Exception.InnerExceptions);
  804. Assert.IsType<NotImplementedException>(ex);
  805. });
  806. }
  807. [Fact, ForceGC]
  808. public Task Then_NoInputValue_NoReturnValue_FaultPreventsFurtherThenStatementsFromExecuting()
  809. {
  810. // Arrange
  811. bool ranContinuation = false;
  812. return TaskHelpers.FromError(new NotImplementedException())
  813. // Act
  814. .Then(() =>
  815. {
  816. ranContinuation = true;
  817. })
  818. // Assert
  819. .ContinueWith(task =>
  820. {
  821. var ex = task.Exception; // Observe the exception
  822. Assert.False(ranContinuation);
  823. });
  824. }
  825. [Fact, ForceGC]
  826. public Task Then_NoInputValue_NoReturnValue_ManualCancellationPreventsFurtherThenStatementsFromExecuting()
  827. {
  828. // Arrange
  829. bool ranContinuation = false;
  830. return TaskHelpers.Canceled()
  831. // Act
  832. .Then(() =>
  833. {
  834. ranContinuation = true;
  835. })
  836. // Assert
  837. .ContinueWith(task =>
  838. {
  839. Assert.Equal(TaskStatus.Canceled, task.Status);
  840. Assert.False(ranContinuation);
  841. });
  842. }
  843. [Fact, ForceGC]
  844. public Task Then_NoInputValue_NoReturnValue_TokenCancellationPreventsFurtherThenStatementsFromExecuting()
  845. {
  846. // Arrange
  847. bool ranContinuation = false;
  848. CancellationToken cancellationToken = new CancellationToken(canceled: true);
  849. return TaskHelpers.Completed()
  850. // Act
  851. .Then(() =>
  852. {
  853. ranContinuation = true;
  854. }, cancellationToken)
  855. // Assert
  856. .ContinueWith(task =>
  857. {
  858. Assert.Equal(TaskStatus.Canceled, task.Status);
  859. Assert.False(ranContinuation);
  860. });
  861. }
  862. [Fact, ForceGC, PreserveSyncContext]
  863. public Task Then_NoInputValue_NoReturnValue_IncompleteTask_RunsOnNewThreadAndPostsContinuationToSynchronizationContext()
  864. {
  865. // Arrange
  866. int originalThreadId = Thread.CurrentThread.ManagedThreadId;
  867. int callbackThreadId = Int32.MinValue;
  868. var syncContext = new Mock<SynchronizationContext> { CallBase = true };
  869. SynchronizationContext.SetSynchronizationContext(syncContext.Object);
  870. Task incompleteTask = new Task(() => { });
  871. // Act
  872. Task resultTask = incompleteTask.Then(() =>
  873. {
  874. callbackThreadId = Thread.CurrentThread.ManagedThreadId;
  875. });
  876. // Assert
  877. incompleteTask.Start();
  878. return resultTask.ContinueWith(task =>
  879. {
  880. Assert.NotEqual(originalThreadId, callbackThreadId);
  881. syncContext.Verify(sc => sc.Post(It.IsAny<SendOrPostCallback>(), null), Times.Once());
  882. });
  883. }
  884. [Fact, ForceGC, PreserveSyncContext]
  885. public Task Then_NoInputValue_NoReturnValue_CompleteTask_RunsOnSameThreadAndDoesNotPostToSynchronizationContext()
  886. {
  887. // Arrange
  888. int originalThreadId = Thread.CurrentThread.ManagedThreadId;
  889. int callbackThreadId = Int32.MinValue;
  890. var syncContext = new Mock<SynchronizationContext> { CallBase = true };
  891. SynchronizationContext.SetSynchronizationContext(syncContext.Object);
  892. return TaskHelpers.Completed()
  893. // Act
  894. .Then(() =>
  895. {
  896. callbackThreadId = Thread.CurrentThread.ManagedThreadId;
  897. })
  898. // Assert
  899. .ContinueWith(task =>
  900. {
  901. Assert.Equal(originalThreadId, callbackThreadId);
  902. syncContext.Verify(sc => sc.Post(It.IsAny<SendOrPostCallback>(), null), Times.Never());
  903. });
  904. }
  905. // -----------------------------------------------------------------
  906. // Task Task.Then(Func<Task>)
  907. [Fact, ForceGC]
  908. public Task Then_NoInputValue_ReturnsTask_CallsContinuation()
  909. {
  910. // Arrange
  911. bool ranContinuation = false;
  912. return TaskHelpers.Completed()
  913. // Act
  914. .Then(() =>
  915. {
  916. ranContinuation = true;
  917. return TaskHelpers.Completed();
  918. })
  919. // Assert
  920. .ContinueWith(task =>
  921. {
  922. Assert.Equal(TaskStatus.RanToCompletion, task.Status);
  923. Assert.True(ranContinuation);
  924. });
  925. }
  926. [Fact, ForceGC]
  927. public Task Then_NoInputValue_ReturnsTask_ThrownExceptionIsRethrowd()
  928. {
  929. // Arrange
  930. return TaskHelpers.Completed()
  931. // Act
  932. .Then(() =>
  933. {
  934. throw new NotImplementedException();
  935. return TaskHelpers.Completed(); // Return-after-throw to guarantee correct lambda signature
  936. })
  937. // Assert
  938. .ContinueWith(task =>
  939. {
  940. Assert.Equal(TaskStatus.Faulted, task.Status);
  941. var ex = Assert.Single(task.Exception.InnerExceptions);
  942. Assert.IsType<NotImplementedException>(ex);
  943. });
  944. }
  945. [Fact, ForceGC]
  946. public Task Then_NoInputValue_ReturnsTask_FaultPreventsFurtherThenStatementsFromExecuting()
  947. {
  948. // Arrange
  949. bool ranContinuation = false;
  950. return TaskHelpers.FromError(new NotImplementedException())
  951. // Act
  952. .Then(() =>
  953. {
  954. ranContinuation = true;
  955. return TaskHelpers.Completed();
  956. })
  957. // Assert
  958. .ContinueWith(task =>
  959. {
  960. var ex = task.Exception; // Observe the exception
  961. Assert.False(ranContinuation);
  962. });
  963. }
  964. [Fact, ForceGC]
  965. public Task Then_NoInputValue_ReturnsTask_ManualCancellationPreventsFurtherThenStatementsFromExecuting()
  966. {
  967. // Arrange
  968. bool ranContinuation = false;
  969. return TaskHelpers.Canceled()
  970. // Act
  971. .Then(() =>
  972. {
  973. ranContinuation = true;
  974. return TaskHelpers.Completed();
  975. })
  976. // Assert
  977. .ContinueWith(task =>
  978. {
  979. Assert.Equal(TaskStatus.Canceled, task.Status);
  980. Assert.False(ranContinuation);
  981. });
  982. }
  983. [Fact, ForceGC]
  984. public Task Then_NoInputValue_ReturnsTask_TokenCancellationPreventsFurtherThenStatementsFromExecuting()
  985. {
  986. // Arrange
  987. bool ranContinuation = false;
  988. CancellationToken cancellationToken = new CancellationToken(canceled: true);
  989. return TaskHelpers.Completed()
  990. // Act
  991. .Then(() =>
  992. {
  993. ranContinuation = true;
  994. return TaskHelpers.Completed();
  995. }, cancellationToken)
  996. // Assert
  997. .ContinueWith(task =>
  998. {
  999. Assert.Equal(TaskStatus.Canceled, task.Status);
  1000. Assert.False(ranContinuation);
  1001. });
  1002. }
  1003. [Fact, ForceGC, PreserveSyncContext]
  1004. public Task Then_NoInputValue_ReturnsTask_IncompleteTask_RunsOnNewThreadAndPostsContinuationToSynchronizationContext()
  1005. {
  1006. // Arrange
  1007. int originalThreadId = Thread.CurrentThread.ManagedThreadId;
  1008. int callbackThreadId = Int32.MinValue;
  1009. var syncContext = new Mock<SynchronizationContext> { CallBase = true };
  1010. SynchronizationContext.SetSynchronizationContext(syncContext.Object);
  1011. Task incompleteTask = new Task(() => { });
  1012. // Act
  1013. Task resultTask = incompleteTask.Then(() =>
  1014. {
  1015. callbackThreadId = Thread.CurrentThread.ManagedThreadId;
  1016. return TaskHelpers.Completed();
  1017. });
  1018. // Assert
  1019. incompleteTask.Start();
  1020. return resultTask.ContinueWith(task =>
  1021. {
  1022. Assert.NotEqual(originalThreadId, callbackThreadId);
  1023. syncContext.Verify(sc => sc.Post(It.IsAny<SendOrPostCallback>(), null), Times.Once());
  1024. });
  1025. }
  1026. [Fact, ForceGC, PreserveSyncContext]
  1027. public Task Then_NoInputValue_ReturnsTask_CompleteTask_RunsOnSameThreadAndDoesNotPostToSynchronizationContext()
  1028. {
  1029. // Arrange
  1030. int originalThreadId = Thread.CurrentThread.ManagedThreadId;
  1031. int callbackThreadId = Int32.MinValue;
  1032. var syncContext = new Mock<SynchronizationContext> { CallBase = true };
  1033. SynchronizationContext.SetSynchronizationContext(syncContext.Object);