Jo Shields a575963da9 Imported Upstream version 3.6.0
Former-commit-id: da6be194a6b1221998fc28233f2503bd61dd9d14
2014-08-13 10:39:27 +01:00

560 lines
20 KiB
C#

// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Moq;
using Xunit;
using Assert = Microsoft.TestCommon.AssertEx;
namespace System.Web.Mvc.Async.Test
{
public class TaskAsyncActionDescriptorTest
{
private readonly MethodInfo _taskMethod = typeof(ExecuteController).GetMethod("SimpleTask");
[Fact]
public void Constructor_SetsProperties()
{
// Arrange
string actionName = "SomeAction";
ControllerDescriptor cd = new Mock<ControllerDescriptor>().Object;
// Act
TaskAsyncActionDescriptor ad = new TaskAsyncActionDescriptor(_taskMethod, actionName, cd);
// Assert
Assert.Equal(_taskMethod, ad.TaskMethodInfo);
Assert.Equal(actionName, ad.ActionName);
Assert.Equal(cd, ad.ControllerDescriptor);
}
[Fact]
public void Constructor_ThrowsIfActionNameIsEmpty()
{
// Arrange
ControllerDescriptor cd = new Mock<ControllerDescriptor>().Object;
// Act & assert
Assert.ThrowsArgumentNullOrEmpty(
delegate { new TaskAsyncActionDescriptor(_taskMethod, "", cd); }, "actionName");
}
[Fact]
public void Constructor_ThrowsIfActionNameIsNull()
{
// Arrange
ControllerDescriptor cd = new Mock<ControllerDescriptor>().Object;
// Act & assert
Assert.ThrowsArgumentNullOrEmpty(
delegate { new TaskAsyncActionDescriptor(_taskMethod, null, cd); }, "actionName");
}
[Fact]
public void Constructor_ThrowsIfTaskMethodInfoIsInvalid()
{
// Arrange
ControllerDescriptor cd = new Mock<ControllerDescriptor>().Object;
MethodInfo getHashCodeMethod = typeof(object).GetMethod("GetHashCode");
// Act & assert
Assert.Throws<ArgumentException>(
delegate { new TaskAsyncActionDescriptor(getHashCodeMethod, "SomeAction", cd); },
@"Cannot create a descriptor for instance method 'Int32 GetHashCode()' on type 'System.Object' because the type does not derive from ControllerBase.
Parameter name: taskMethodInfo");
}
[Fact]
public void Constructor_ThrowsIfTaskMethodInfoIsNull()
{
// Arrange
ControllerDescriptor cd = new Mock<ControllerDescriptor>().Object;
// Act & assert
Assert.ThrowsArgumentNull(
delegate { new TaskAsyncActionDescriptor(null, "SomeAction", cd); }, "taskMethodInfo");
}
[Fact]
public void Constructor_ThrowsIfControllerDescriptorIsNull()
{
// Act & assert
Assert.ThrowsArgumentNull(
delegate { new TaskAsyncActionDescriptor(_taskMethod, "SomeAction", null); }, "controllerDescriptor");
}
[Fact]
public void ExecuteTask()
{
// Arrange
TaskAsyncActionDescriptor actionDescriptor = GetActionDescriptor(GetExecuteControllerMethodInfo("SimpleTask"));
Dictionary<string, object> parameters = new Dictionary<string, object>()
{
{ "doWork", true }
};
ControllerContext controllerContext = GetControllerContext();
// Act
object retVal = ExecuteHelper(actionDescriptor, parameters, controllerContext);
// Assert
Assert.Null(retVal);
Assert.True((controllerContext.Controller as ExecuteController).WorkDone);
}
[Fact]
public void ExecuteTaskGeneric()
{
// Arrange
TaskAsyncActionDescriptor actionDescriptor = GetActionDescriptor(GetExecuteControllerMethodInfo("GenericTask"));
Dictionary<string, object> parameters = new Dictionary<string, object>()
{
{ "taskId", "foo" }
};
// Act
object retVal = ExecuteHelper(actionDescriptor, parameters);
// Assert
Assert.Equal("foo", retVal);
}
[Fact]
public void ExecuteTaskPreservesStackTraceOnException()
{
// Arrange
TaskAsyncActionDescriptor actionDescriptor = GetActionDescriptor(GetExecuteControllerMethodInfo("SimpleTaskException"));
Dictionary<string, object> parameters = new Dictionary<string, object>()
{
{ "doWork", true }
};
// Act
IAsyncResult result = actionDescriptor.BeginExecute(GetControllerContext(), parameters, null, null);
// Assert
InvalidOperationException ex = Assert.Throws<InvalidOperationException>(
() => actionDescriptor.EndExecute(result),
"Test exception from action"
);
Assert.True(ex.StackTrace.Contains("System.Web.Mvc.Async.Test.TaskAsyncActionDescriptorTest.ExecuteController."));
}
[Fact]
public void ExecuteTaskGenericPreservesStackTraceOnException()
{
// Arrange
TaskAsyncActionDescriptor actionDescriptor = GetActionDescriptor(GetExecuteControllerMethodInfo("GenericTaskException"));
Dictionary<string, object> parameters = new Dictionary<string, object>()
{
{ "taskId", "foo" },
{ "throwException", true }
};
// Act
IAsyncResult result = actionDescriptor.BeginExecute(GetControllerContext(), parameters, null, null);
// Assert
InvalidOperationException ex = Assert.Throws<InvalidOperationException>(
() => actionDescriptor.EndExecute(result),
"Test exception from action"
);
Assert.True(ex.StackTrace.Contains("System.Web.Mvc.Async.Test.TaskAsyncActionDescriptorTest.ExecuteController."));
}
[Fact]
public void ExecuteTaskOfPrivateT()
{
// Arrange
TaskAsyncActionDescriptor actionDescriptor = GetActionDescriptor(GetExecuteControllerMethodInfo("TaskOfPrivateT"));
ControllerContext controllerContext = GetControllerContext();
Dictionary<string, object> parameters = new Dictionary<string, object>();
// Act
object retVal = ExecuteHelper(actionDescriptor, parameters, controllerContext);
// Assert
Assert.Null(retVal);
Assert.True((controllerContext.Controller as ExecuteController).WorkDone);
}
[Fact]
public void ExecuteTaskPreservesState()
{
// Arrange
TaskAsyncActionDescriptor actionDescriptor = GetActionDescriptor(GetExecuteControllerMethodInfo("SimpleTask"));
Dictionary<string, object> parameters = new Dictionary<string, object>()
{
{ "doWork", true }
};
ControllerContext controllerContext = GetControllerContext();
// Act
TaskWrapperAsyncResult result = (TaskWrapperAsyncResult)actionDescriptor.BeginExecute(GetControllerContext(), parameters, callback: null, state: "state");
// Assert
Assert.Equal("state", result.AsyncState);
}
[Fact]
public void ExecuteTaskWithNullParameterAndTimeout()
{
// Arrange
TaskAsyncActionDescriptor actionDescriptor = GetActionDescriptor(GetExecuteControllerMethodInfo("TaskTimeoutWithNullParam"));
Dictionary<string, object> token = new Dictionary<string, object>()
{
{ "nullParam", null },
{ "cancellationToken", new CancellationToken() }
};
// Act & assert
Assert.Throws<TimeoutException>(
() => actionDescriptor.EndExecute(actionDescriptor.BeginExecute(GetControllerContext(0), parameters: token, callback: null, state: null)),
"The operation has timed out."
);
}
[Fact]
public void ExecuteWithInfiniteTimeout()
{
// Arrange
TaskAsyncActionDescriptor actionDescriptor = GetActionDescriptor(GetExecuteControllerMethodInfo("TaskWithInfiniteTimeout"));
ControllerContext controllerContext = GetControllerContext(Timeout.Infinite);
Dictionary<string, object> parameters = new Dictionary<string, object>()
{
{ "cancellationToken", new CancellationToken() }
};
// Act
object retVal = ExecuteHelper(actionDescriptor, parameters);
// Assert
Assert.Equal("Task Completed", retVal);
}
[Fact]
public void ExecuteTaskWithImmediateTimeout()
{
// Arrange
TaskAsyncActionDescriptor actionDescriptor = GetActionDescriptor(GetExecuteControllerMethodInfo("TaskTimeout"));
Dictionary<string, object> token = new Dictionary<string, object>()
{
{ "cancellationToken", new CancellationToken() }
};
// Act & assert
Assert.Throws<TimeoutException>(
() => actionDescriptor.EndExecute(actionDescriptor.BeginExecute(GetControllerContext(0), parameters: token, callback: null, state: null)),
"The operation has timed out."
);
}
[Fact]
public void ExecuteTaskWithTimeout()
{
// Arrange
TaskAsyncActionDescriptor actionDescriptor = GetActionDescriptor(GetExecuteControllerMethodInfo("TaskTimeout"));
Dictionary<string, object> token = new Dictionary<string, object>()
{
{ "cancellationToken", new CancellationToken() }
};
// Act & assert
Assert.Throws<TimeoutException>(
() => actionDescriptor.EndExecute(actionDescriptor.BeginExecute(GetControllerContext(2000), parameters: token, callback: null, state: null)),
"The operation has timed out."
);
}
[Fact]
public void SynchronousExecuteThrows()
{
// Arrange
TaskAsyncActionDescriptor actionDescriptor = GetActionDescriptor(GetExecuteControllerMethodInfo("SimpleTask"));
// Act & assert
Assert.Throws<InvalidOperationException>(
delegate { actionDescriptor.Execute(new ControllerContext(), new Dictionary<string, object>()); }, "The asynchronous action method 'someName' returns a Task, which cannot be executed synchronously.");
}
[Fact]
public void Execute_ThrowsIfControllerContextIsNull()
{
// Arrange
TaskAsyncActionDescriptor ad = GetActionDescriptor(_taskMethod);
// Act & assert
Assert.ThrowsArgumentNull(
delegate { ad.BeginExecute(null, new Dictionary<string, object>(), null, null); }, "controllerContext");
}
[Fact]
public void Execute_ThrowsIfControllerIsNotAsyncManagerContainer()
{
// Arrange
TaskAsyncActionDescriptor ad = GetActionDescriptor(_taskMethod);
ControllerContext controllerContext = new ControllerContext()
{
Controller = new RegularSyncController()
};
Dictionary<string, object> parameters = new Dictionary<string, object>()
{
{ "doWork", true }
};
// Act & assert
Assert.Throws<InvalidOperationException>(
delegate { ad.BeginExecute(controllerContext, parameters, null, null); },
@"The controller of type 'System.Web.Mvc.Async.Test.TaskAsyncActionDescriptorTest+RegularSyncController' must subclass AsyncController or implement the IAsyncManagerContainer interface.");
}
[Fact]
public void Execute_ThrowsIfParametersIsNull()
{
// Arrange
TaskAsyncActionDescriptor ad = GetActionDescriptor(_taskMethod);
// Act & assert
Assert.ThrowsArgumentNull(
delegate { ad.BeginExecute(new ControllerContext(), null, null, null); }, "parameters");
}
[Fact]
public void GetCustomAttributesCallsMethodInfoGetCustomAttributes()
{
// Arrange
object[] expected = new object[0];
Mock<MethodInfo> mockMethod = new Mock<MethodInfo>();
mockMethod.Setup(mi => mi.GetCustomAttributes(true)).Returns(expected);
TaskAsyncActionDescriptor ad = new TaskAsyncActionDescriptor(mockMethod.Object, "someName", new Mock<ControllerDescriptor>().Object, validateMethod: false)
{
DispatcherCache = new ActionMethodDispatcherCache()
};
// Act
object[] returned = ad.GetCustomAttributes(true);
// Assert
Assert.Same(expected, returned);
}
[Fact]
public void GetCustomAttributesWithAttributeTypeCallsMethodInfoGetCustomAttributes()
{
// Arrange
object[] expected = new object[0];
Mock<MethodInfo> mockMethod = new Mock<MethodInfo>();
mockMethod.Setup(mi => mi.GetCustomAttributes(typeof(ObsoleteAttribute), true)).Returns(expected);
TaskAsyncActionDescriptor ad = new TaskAsyncActionDescriptor(mockMethod.Object, "someName", new Mock<ControllerDescriptor>().Object, validateMethod: false)
{
DispatcherCache = new ActionMethodDispatcherCache()
};
// Act
object[] returned = ad.GetCustomAttributes(typeof(ObsoleteAttribute), true);
// Assert
Assert.Same(expected, returned);
}
[Fact]
public void GetParameters()
{
// Arrange
ParameterInfo pInfo = _taskMethod.GetParameters()[0];
TaskAsyncActionDescriptor ad = GetActionDescriptor(_taskMethod);
// Act
ParameterDescriptor[] pDescsFirstCall = ad.GetParameters();
ParameterDescriptor[] pDescsSecondCall = ad.GetParameters();
// Assert
Assert.NotSame(pDescsFirstCall, pDescsSecondCall); // Should get a new array every time
Assert.Equal(pDescsFirstCall, pDescsSecondCall);
Assert.Single(pDescsFirstCall);
ReflectedParameterDescriptor pDesc = pDescsFirstCall[0] as ReflectedParameterDescriptor;
Assert.NotNull(pDesc);
Assert.Same(ad, pDesc.ActionDescriptor);
Assert.Same(pInfo, pDesc.ParameterInfo);
}
[Fact]
public void GetSelectors()
{
// Arrange
ControllerContext controllerContext = new Mock<ControllerContext>().Object;
Mock<MethodInfo> mockMethod = new Mock<MethodInfo>();
Mock<ActionMethodSelectorAttribute> mockAttr = new Mock<ActionMethodSelectorAttribute>();
mockAttr.Setup(attr => attr.IsValidForRequest(controllerContext, mockMethod.Object)).Returns(true).Verifiable();
mockMethod.Setup(m => m.GetCustomAttributes(typeof(ActionMethodSelectorAttribute), true)).Returns(new ActionMethodSelectorAttribute[] { mockAttr.Object });
TaskAsyncActionDescriptor ad = new TaskAsyncActionDescriptor(mockMethod.Object, "someName", new Mock<ControllerDescriptor>().Object, validateMethod: false)
{
DispatcherCache = new ActionMethodDispatcherCache()
};
// Act
ICollection<ActionSelector> selectors = ad.GetSelectors();
bool executedSuccessfully = selectors.All(s => s(controllerContext));
// Assert
Assert.Single(selectors);
Assert.True(executedSuccessfully);
mockAttr.Verify();
}
[Fact]
public void IsDefined()
{
// Arrange
TaskAsyncActionDescriptor ad = GetActionDescriptor(_taskMethod);
// Act
bool isDefined = ad.IsDefined(typeof(AuthorizeAttribute), inherit: true);
// Assert
Assert.True(isDefined);
}
public static object ExecuteHelper(TaskAsyncActionDescriptor actionDescriptor, Dictionary<string, object> parameters, ControllerContext controllerContext = null)
{
SignalContainer<object> resultContainer = new SignalContainer<object>();
AsyncCallback callback = ar =>
{
object o = actionDescriptor.EndExecute(ar);
resultContainer.Signal(o);
};
actionDescriptor.BeginExecute(controllerContext ?? GetControllerContext(), parameters, callback, state: null);
return resultContainer.Wait();
}
private static TaskAsyncActionDescriptor GetActionDescriptor(MethodInfo taskMethod)
{
return new TaskAsyncActionDescriptor(taskMethod, "someName", new Mock<ControllerDescriptor>().Object)
{
DispatcherCache = new ActionMethodDispatcherCache()
};
}
private static ControllerContext GetControllerContext(int timeout = 45 * 1000)
{
Mock<ControllerContext> mockControllerContext = new Mock<ControllerContext>();
ExecuteController controller = new ExecuteController();
controller.AsyncManager.Timeout = timeout;
mockControllerContext.Setup(c => c.Controller).Returns(controller);
return mockControllerContext.Object;
}
private static MethodInfo GetExecuteControllerMethodInfo(string methodName)
{
return typeof(ExecuteController).GetMethod(methodName);
}
private class ExecuteController : AsyncController
{
public bool WorkDone { get; set; }
public Task<ActionResult> ReturnedTask { get; set; }
public Task<string> GenericTask(string taskId)
{
return Task.Factory.StartNew(() => taskId);
}
public Task<string> GenericTaskException(string taskId, bool throwException)
{
return Task.Factory.StartNew(() =>
{
if (throwException)
{
ThrowException();
}
;
return taskId;
});
}
private void ThrowException()
{
throw new InvalidOperationException("Test exception from action");
}
[Authorize]
public Task SimpleTask(bool doWork)
{
return Task.Factory.StartNew(() => { WorkDone = doWork; });
}
public Task SimpleTaskException(bool doWork)
{
return Task.Factory.StartNew(() => { ThrowException(); });
}
public Task<ActionResult> TaskTimeoutWithNullParam(Object nullParam, CancellationToken cancellationToken)
{
return TaskTimeout(cancellationToken);
}
public Task<string> TaskWithInfiniteTimeout(CancellationToken cancellationToken)
{
return Task.Factory.StartNew(() => "Task Completed");
}
public Task<ActionResult> TaskTimeout(CancellationToken cancellationToken)
{
TaskCompletionSource<ActionResult> completionSource = new TaskCompletionSource<ActionResult>();
cancellationToken.Register(() => completionSource.TrySetCanceled());
ReturnedTask = completionSource.Task;
return ReturnedTask;
}
public Task TaskOfPrivateT()
{
var completionSource = new TaskCompletionSource<PrivateObject>();
completionSource.SetResult(new PrivateObject());
WorkDone = true;
return completionSource.Task;
}
private class PrivateObject
{
public override string ToString()
{
return "Private Object";
}
}
}
// Controller is async, so derive from ControllerBase to get sync behavior.
private class RegularSyncController : ControllerBase
{
protected override void ExecuteCore()
{
throw new NotImplementedException();
}
}
}
}