PageRenderTime 62ms CodeModel.GetById 18ms RepoModel.GetById 1ms app.codeStats 0ms

/test/System.Web.Mvc.Test/Async/Test/TaskAsyncActionDescriptorTest.cs

https://bitbucket.org/mdavid/aspnetwebstack
C# | 557 lines | 394 code | 100 blank | 63 comment | 1 complexity | 461114e2f045f88bc85eee6c6cede88b MD5 | raw file
  1. using System.Collections.Generic;
  2. using System.Linq;
  3. using System.Reflection;
  4. using System.Threading;
  5. using System.Threading.Tasks;
  6. using Moq;
  7. using Xunit;
  8. using Assert = Microsoft.TestCommon.AssertEx;
  9. namespace System.Web.Mvc.Async.Test
  10. {
  11. public class TaskAsyncActionDescriptorTest
  12. {
  13. private readonly MethodInfo _taskMethod = typeof(ExecuteController).GetMethod("SimpleTask");
  14. [Fact]
  15. public void Constructor_SetsProperties()
  16. {
  17. // Arrange
  18. string actionName = "SomeAction";
  19. ControllerDescriptor cd = new Mock<ControllerDescriptor>().Object;
  20. // Act
  21. TaskAsyncActionDescriptor ad = new TaskAsyncActionDescriptor(_taskMethod, actionName, cd);
  22. // Assert
  23. Assert.Equal(_taskMethod, ad.TaskMethodInfo);
  24. Assert.Equal(actionName, ad.ActionName);
  25. Assert.Equal(cd, ad.ControllerDescriptor);
  26. }
  27. [Fact]
  28. public void Constructor_ThrowsIfActionNameIsEmpty()
  29. {
  30. // Arrange
  31. ControllerDescriptor cd = new Mock<ControllerDescriptor>().Object;
  32. // Act & assert
  33. Assert.ThrowsArgumentNullOrEmpty(
  34. delegate { new TaskAsyncActionDescriptor(_taskMethod, "", cd); }, "actionName");
  35. }
  36. [Fact]
  37. public void Constructor_ThrowsIfActionNameIsNull()
  38. {
  39. // Arrange
  40. ControllerDescriptor cd = new Mock<ControllerDescriptor>().Object;
  41. // Act & assert
  42. Assert.ThrowsArgumentNullOrEmpty(
  43. delegate { new TaskAsyncActionDescriptor(_taskMethod, null, cd); }, "actionName");
  44. }
  45. [Fact]
  46. public void Constructor_ThrowsIfTaskMethodInfoIsInvalid()
  47. {
  48. // Arrange
  49. ControllerDescriptor cd = new Mock<ControllerDescriptor>().Object;
  50. MethodInfo getHashCodeMethod = typeof(object).GetMethod("GetHashCode");
  51. // Act & assert
  52. Assert.Throws<ArgumentException>(
  53. delegate { new TaskAsyncActionDescriptor(getHashCodeMethod, "SomeAction", cd); },
  54. @"Cannot create a descriptor for instance method 'Int32 GetHashCode()' on type 'System.Object' because the type does not derive from ControllerBase.
  55. Parameter name: taskMethodInfo");
  56. }
  57. [Fact]
  58. public void Constructor_ThrowsIfTaskMethodInfoIsNull()
  59. {
  60. // Arrange
  61. ControllerDescriptor cd = new Mock<ControllerDescriptor>().Object;
  62. // Act & assert
  63. Assert.ThrowsArgumentNull(
  64. delegate { new TaskAsyncActionDescriptor(null, "SomeAction", cd); }, "taskMethodInfo");
  65. }
  66. [Fact]
  67. public void Constructor_ThrowsIfControllerDescriptorIsNull()
  68. {
  69. // Act & assert
  70. Assert.ThrowsArgumentNull(
  71. delegate { new TaskAsyncActionDescriptor(_taskMethod, "SomeAction", null); }, "controllerDescriptor");
  72. }
  73. [Fact]
  74. public void ExecuteTask()
  75. {
  76. // Arrange
  77. TaskAsyncActionDescriptor actionDescriptor = GetActionDescriptor(GetExecuteControllerMethodInfo("SimpleTask"));
  78. Dictionary<string, object> parameters = new Dictionary<string, object>()
  79. {
  80. { "doWork", true }
  81. };
  82. ControllerContext controllerContext = GetControllerContext();
  83. // Act
  84. object retVal = ExecuteHelper(actionDescriptor, parameters, controllerContext);
  85. // Assert
  86. Assert.Null(retVal);
  87. Assert.True((controllerContext.Controller as ExecuteController).WorkDone);
  88. }
  89. [Fact]
  90. public void ExecuteTaskGeneric()
  91. {
  92. // Arrange
  93. TaskAsyncActionDescriptor actionDescriptor = GetActionDescriptor(GetExecuteControllerMethodInfo("GenericTask"));
  94. Dictionary<string, object> parameters = new Dictionary<string, object>()
  95. {
  96. { "taskId", "foo" }
  97. };
  98. // Act
  99. object retVal = ExecuteHelper(actionDescriptor, parameters);
  100. // Assert
  101. Assert.Equal("foo", retVal);
  102. }
  103. [Fact]
  104. public void ExecuteTaskPreservesStackTraceOnException()
  105. {
  106. // Arrange
  107. TaskAsyncActionDescriptor actionDescriptor = GetActionDescriptor(GetExecuteControllerMethodInfo("SimpleTaskException"));
  108. Dictionary<string, object> parameters = new Dictionary<string, object>()
  109. {
  110. { "doWork", true }
  111. };
  112. // Act
  113. IAsyncResult result = actionDescriptor.BeginExecute(GetControllerContext(), parameters, null, null);
  114. // Assert
  115. InvalidOperationException ex = Assert.Throws<InvalidOperationException>(
  116. () => actionDescriptor.EndExecute(result),
  117. "Test exception from action"
  118. );
  119. Assert.True(ex.StackTrace.Contains("System.Web.Mvc.Async.Test.TaskAsyncActionDescriptorTest.ExecuteController."));
  120. }
  121. [Fact]
  122. public void ExecuteTaskGenericPreservesStackTraceOnException()
  123. {
  124. // Arrange
  125. TaskAsyncActionDescriptor actionDescriptor = GetActionDescriptor(GetExecuteControllerMethodInfo("GenericTaskException"));
  126. Dictionary<string, object> parameters = new Dictionary<string, object>()
  127. {
  128. { "taskId", "foo" },
  129. { "throwException", true }
  130. };
  131. // Act
  132. IAsyncResult result = actionDescriptor.BeginExecute(GetControllerContext(), parameters, null, null);
  133. // Assert
  134. InvalidOperationException ex = Assert.Throws<InvalidOperationException>(
  135. () => actionDescriptor.EndExecute(result),
  136. "Test exception from action"
  137. );
  138. Assert.True(ex.StackTrace.Contains("System.Web.Mvc.Async.Test.TaskAsyncActionDescriptorTest.ExecuteController."));
  139. }
  140. [Fact]
  141. public void ExecuteTaskOfPrivateT()
  142. {
  143. // Arrange
  144. TaskAsyncActionDescriptor actionDescriptor = GetActionDescriptor(GetExecuteControllerMethodInfo("TaskOfPrivateT"));
  145. ControllerContext controllerContext = GetControllerContext();
  146. Dictionary<string, object> parameters = new Dictionary<string, object>();
  147. // Act
  148. object retVal = ExecuteHelper(actionDescriptor, parameters, controllerContext);
  149. // Assert
  150. Assert.Null(retVal);
  151. Assert.True((controllerContext.Controller as ExecuteController).WorkDone);
  152. }
  153. [Fact]
  154. public void ExecuteTaskPreservesState()
  155. {
  156. // Arrange
  157. TaskAsyncActionDescriptor actionDescriptor = GetActionDescriptor(GetExecuteControllerMethodInfo("SimpleTask"));
  158. Dictionary<string, object> parameters = new Dictionary<string, object>()
  159. {
  160. { "doWork", true }
  161. };
  162. ControllerContext controllerContext = GetControllerContext();
  163. // Act
  164. TaskWrapperAsyncResult result = (TaskWrapperAsyncResult)actionDescriptor.BeginExecute(GetControllerContext(), parameters, callback: null, state: "state");
  165. // Assert
  166. Assert.Equal("state", result.AsyncState);
  167. }
  168. [Fact]
  169. public void ExecuteTaskWithNullParameterAndTimeout()
  170. {
  171. // Arrange
  172. TaskAsyncActionDescriptor actionDescriptor = GetActionDescriptor(GetExecuteControllerMethodInfo("TaskTimeoutWithNullParam"));
  173. Dictionary<string, object> token = new Dictionary<string, object>()
  174. {
  175. { "nullParam", null },
  176. { "cancellationToken", new CancellationToken() }
  177. };
  178. // Act & assert
  179. Assert.Throws<TimeoutException>(
  180. () => actionDescriptor.EndExecute(actionDescriptor.BeginExecute(GetControllerContext(0), parameters: token, callback: null, state: null)),
  181. "The operation has timed out."
  182. );
  183. }
  184. [Fact]
  185. public void ExecuteWithInfiniteTimeout()
  186. {
  187. // Arrange
  188. TaskAsyncActionDescriptor actionDescriptor = GetActionDescriptor(GetExecuteControllerMethodInfo("TaskWithInfiniteTimeout"));
  189. ControllerContext controllerContext = GetControllerContext(Timeout.Infinite);
  190. Dictionary<string, object> parameters = new Dictionary<string, object>()
  191. {
  192. { "cancellationToken", new CancellationToken() }
  193. };
  194. // Act
  195. object retVal = ExecuteHelper(actionDescriptor, parameters);
  196. // Assert
  197. Assert.Equal("Task Completed", retVal);
  198. }
  199. [Fact]
  200. public void ExecuteTaskWithImmediateTimeout()
  201. {
  202. // Arrange
  203. TaskAsyncActionDescriptor actionDescriptor = GetActionDescriptor(GetExecuteControllerMethodInfo("TaskTimeout"));
  204. Dictionary<string, object> token = new Dictionary<string, object>()
  205. {
  206. { "cancellationToken", new CancellationToken() }
  207. };
  208. // Act & assert
  209. Assert.Throws<TimeoutException>(
  210. () => actionDescriptor.EndExecute(actionDescriptor.BeginExecute(GetControllerContext(0), parameters: token, callback: null, state: null)),
  211. "The operation has timed out."
  212. );
  213. }
  214. [Fact]
  215. public void ExecuteTaskWithTimeout()
  216. {
  217. // Arrange
  218. TaskAsyncActionDescriptor actionDescriptor = GetActionDescriptor(GetExecuteControllerMethodInfo("TaskTimeout"));
  219. Dictionary<string, object> token = new Dictionary<string, object>()
  220. {
  221. { "cancellationToken", new CancellationToken() }
  222. };
  223. // Act & assert
  224. Assert.Throws<TimeoutException>(
  225. () => actionDescriptor.EndExecute(actionDescriptor.BeginExecute(GetControllerContext(2000), parameters: token, callback: null, state: null)),
  226. "The operation has timed out."
  227. );
  228. }
  229. [Fact]
  230. public void SynchronousExecuteThrows()
  231. {
  232. // Arrange
  233. TaskAsyncActionDescriptor actionDescriptor = GetActionDescriptor(GetExecuteControllerMethodInfo("SimpleTask"));
  234. // Act & assert
  235. Assert.Throws<InvalidOperationException>(
  236. delegate { actionDescriptor.Execute(new ControllerContext(), new Dictionary<string, object>()); }, "The asynchronous action method 'someName' returns a Task, which cannot be executed synchronously.");
  237. }
  238. [Fact]
  239. public void Execute_ThrowsIfControllerContextIsNull()
  240. {
  241. // Arrange
  242. TaskAsyncActionDescriptor ad = GetActionDescriptor(_taskMethod);
  243. // Act & assert
  244. Assert.ThrowsArgumentNull(
  245. delegate { ad.BeginExecute(null, new Dictionary<string, object>(), null, null); }, "controllerContext");
  246. }
  247. [Fact]
  248. public void Execute_ThrowsIfControllerIsNotAsyncManagerContainer()
  249. {
  250. // Arrange
  251. TaskAsyncActionDescriptor ad = GetActionDescriptor(_taskMethod);
  252. ControllerContext controllerContext = new ControllerContext()
  253. {
  254. Controller = new RegularSyncController()
  255. };
  256. Dictionary<string, object> parameters = new Dictionary<string, object>()
  257. {
  258. { "doWork", true }
  259. };
  260. // Act & assert
  261. Assert.Throws<InvalidOperationException>(
  262. delegate { ad.BeginExecute(controllerContext, parameters, null, null); },
  263. @"The controller of type 'System.Web.Mvc.Async.Test.TaskAsyncActionDescriptorTest+RegularSyncController' must subclass AsyncController or implement the IAsyncManagerContainer interface.");
  264. }
  265. [Fact]
  266. public void Execute_ThrowsIfParametersIsNull()
  267. {
  268. // Arrange
  269. TaskAsyncActionDescriptor ad = GetActionDescriptor(_taskMethod);
  270. // Act & assert
  271. Assert.ThrowsArgumentNull(
  272. delegate { ad.BeginExecute(new ControllerContext(), null, null, null); }, "parameters");
  273. }
  274. [Fact]
  275. public void GetCustomAttributesCallsMethodInfoGetCustomAttributes()
  276. {
  277. // Arrange
  278. object[] expected = new object[0];
  279. Mock<MethodInfo> mockMethod = new Mock<MethodInfo>();
  280. mockMethod.Setup(mi => mi.GetCustomAttributes(true)).Returns(expected);
  281. TaskAsyncActionDescriptor ad = new TaskAsyncActionDescriptor(mockMethod.Object, "someName", new Mock<ControllerDescriptor>().Object, validateMethod: false)
  282. {
  283. DispatcherCache = new ActionMethodDispatcherCache()
  284. };
  285. // Act
  286. object[] returned = ad.GetCustomAttributes(true);
  287. // Assert
  288. Assert.Same(expected, returned);
  289. }
  290. [Fact]
  291. public void GetCustomAttributesWithAttributeTypeCallsMethodInfoGetCustomAttributes()
  292. {
  293. // Arrange
  294. object[] expected = new object[0];
  295. Mock<MethodInfo> mockMethod = new Mock<MethodInfo>();
  296. mockMethod.Setup(mi => mi.GetCustomAttributes(typeof(ObsoleteAttribute), true)).Returns(expected);
  297. TaskAsyncActionDescriptor ad = new TaskAsyncActionDescriptor(mockMethod.Object, "someName", new Mock<ControllerDescriptor>().Object, validateMethod: false)
  298. {
  299. DispatcherCache = new ActionMethodDispatcherCache()
  300. };
  301. // Act
  302. object[] returned = ad.GetCustomAttributes(typeof(ObsoleteAttribute), true);
  303. // Assert
  304. Assert.Same(expected, returned);
  305. }
  306. [Fact]
  307. public void GetParameters()
  308. {
  309. // Arrange
  310. ParameterInfo pInfo = _taskMethod.GetParameters()[0];
  311. TaskAsyncActionDescriptor ad = GetActionDescriptor(_taskMethod);
  312. // Act
  313. ParameterDescriptor[] pDescsFirstCall = ad.GetParameters();
  314. ParameterDescriptor[] pDescsSecondCall = ad.GetParameters();
  315. // Assert
  316. Assert.NotSame(pDescsFirstCall, pDescsSecondCall); // Should get a new array every time
  317. Assert.Equal(pDescsFirstCall, pDescsSecondCall);
  318. Assert.Single(pDescsFirstCall);
  319. ReflectedParameterDescriptor pDesc = pDescsFirstCall[0] as ReflectedParameterDescriptor;
  320. Assert.NotNull(pDesc);
  321. Assert.Same(ad, pDesc.ActionDescriptor);
  322. Assert.Same(pInfo, pDesc.ParameterInfo);
  323. }
  324. [Fact]
  325. public void GetSelectors()
  326. {
  327. // Arrange
  328. ControllerContext controllerContext = new Mock<ControllerContext>().Object;
  329. Mock<MethodInfo> mockMethod = new Mock<MethodInfo>();
  330. Mock<ActionMethodSelectorAttribute> mockAttr = new Mock<ActionMethodSelectorAttribute>();
  331. mockAttr.Setup(attr => attr.IsValidForRequest(controllerContext, mockMethod.Object)).Returns(true).Verifiable();
  332. mockMethod.Setup(m => m.GetCustomAttributes(typeof(ActionMethodSelectorAttribute), true)).Returns(new ActionMethodSelectorAttribute[] { mockAttr.Object });
  333. TaskAsyncActionDescriptor ad = new TaskAsyncActionDescriptor(mockMethod.Object, "someName", new Mock<ControllerDescriptor>().Object, validateMethod: false)
  334. {
  335. DispatcherCache = new ActionMethodDispatcherCache()
  336. };
  337. // Act
  338. ICollection<ActionSelector> selectors = ad.GetSelectors();
  339. bool executedSuccessfully = selectors.All(s => s(controllerContext));
  340. // Assert
  341. Assert.Single(selectors);
  342. Assert.True(executedSuccessfully);
  343. mockAttr.Verify();
  344. }
  345. [Fact]
  346. public void IsDefined()
  347. {
  348. // Arrange
  349. TaskAsyncActionDescriptor ad = GetActionDescriptor(_taskMethod);
  350. // Act
  351. bool isDefined = ad.IsDefined(typeof(AuthorizeAttribute), inherit: true);
  352. // Assert
  353. Assert.True(isDefined);
  354. }
  355. public static object ExecuteHelper(TaskAsyncActionDescriptor actionDescriptor, Dictionary<string, object> parameters, ControllerContext controllerContext = null)
  356. {
  357. SignalContainer<object> resultContainer = new SignalContainer<object>();
  358. AsyncCallback callback = ar =>
  359. {
  360. object o = actionDescriptor.EndExecute(ar);
  361. resultContainer.Signal(o);
  362. };
  363. actionDescriptor.BeginExecute(controllerContext ?? GetControllerContext(), parameters, callback, state: null);
  364. return resultContainer.Wait();
  365. }
  366. private static TaskAsyncActionDescriptor GetActionDescriptor(MethodInfo taskMethod)
  367. {
  368. return new TaskAsyncActionDescriptor(taskMethod, "someName", new Mock<ControllerDescriptor>().Object)
  369. {
  370. DispatcherCache = new ActionMethodDispatcherCache()
  371. };
  372. }
  373. private static ControllerContext GetControllerContext(int timeout = 45 * 1000)
  374. {
  375. Mock<ControllerContext> mockControllerContext = new Mock<ControllerContext>();
  376. ExecuteController controller = new ExecuteController();
  377. controller.AsyncManager.Timeout = timeout;
  378. mockControllerContext.Setup(c => c.Controller).Returns(controller);
  379. return mockControllerContext.Object;
  380. }
  381. private static MethodInfo GetExecuteControllerMethodInfo(string methodName)
  382. {
  383. return typeof(ExecuteController).GetMethod(methodName);
  384. }
  385. private class ExecuteController : AsyncController
  386. {
  387. public bool WorkDone { get; set; }
  388. public Task<ActionResult> ReturnedTask { get; set; }
  389. public Task<string> GenericTask(string taskId)
  390. {
  391. return Task.Factory.StartNew(() => taskId);
  392. }
  393. public Task<string> GenericTaskException(string taskId, bool throwException)
  394. {
  395. return Task.Factory.StartNew(() =>
  396. {
  397. if (throwException)
  398. {
  399. ThrowException();
  400. }
  401. ;
  402. return taskId;
  403. });
  404. }
  405. private void ThrowException()
  406. {
  407. throw new InvalidOperationException("Test exception from action");
  408. }
  409. [Authorize]
  410. public Task SimpleTask(bool doWork)
  411. {
  412. return Task.Factory.StartNew(() => { WorkDone = doWork; });
  413. }
  414. public Task SimpleTaskException(bool doWork)
  415. {
  416. return Task.Factory.StartNew(() => { ThrowException(); });
  417. }
  418. public Task<ActionResult> TaskTimeoutWithNullParam(Object nullParam, CancellationToken cancellationToken)
  419. {
  420. return TaskTimeout(cancellationToken);
  421. }
  422. public Task<string> TaskWithInfiniteTimeout(CancellationToken cancellationToken)
  423. {
  424. return Task.Factory.StartNew(() => "Task Completed");
  425. }
  426. public Task<ActionResult> TaskTimeout(CancellationToken cancellationToken)
  427. {
  428. TaskCompletionSource<ActionResult> completionSource = new TaskCompletionSource<ActionResult>();
  429. cancellationToken.Register(() => completionSource.TrySetCanceled());
  430. ReturnedTask = completionSource.Task;
  431. return ReturnedTask;
  432. }
  433. public Task TaskOfPrivateT()
  434. {
  435. var completionSource = new TaskCompletionSource<PrivateObject>();
  436. completionSource.SetResult(new PrivateObject());
  437. WorkDone = true;
  438. return completionSource.Task;
  439. }
  440. private class PrivateObject
  441. {
  442. public override string ToString()
  443. {
  444. return "Private Object";
  445. }
  446. }
  447. }
  448. // Controller is async, so derive from ControllerBase to get sync behavior.
  449. private class RegularSyncController : ControllerBase
  450. {
  451. protected override void ExecuteCore()
  452. {
  453. throw new NotImplementedException();
  454. }
  455. }
  456. }
  457. }