635 lines
22 KiB
C#
635 lines
22 KiB
C#
// Copyright (c) .NET Foundation. All rights reserved.
|
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
|
|
|
using Microsoft.FSharp.Control;
|
|
using Microsoft.FSharp.Core;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Reflection;
|
|
using System.Runtime.CompilerServices;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using Xunit;
|
|
|
|
namespace Microsoft.Extensions.Internal
|
|
{
|
|
public class ObjectMethodExecutorTest
|
|
{
|
|
private TestObject _targetObject = new TestObject();
|
|
private TypeInfo targetTypeInfo = typeof(TestObject).GetTypeInfo();
|
|
|
|
[Fact]
|
|
public void ExecuteValueMethod()
|
|
{
|
|
var executor = GetExecutorForMethod("ValueMethod");
|
|
var result = executor.Execute(
|
|
_targetObject,
|
|
new object[] { 10, 20 });
|
|
Assert.False(executor.IsMethodAsync);
|
|
Assert.Equal(30, (int)result);
|
|
}
|
|
|
|
[Fact]
|
|
public void ExecuteVoidValueMethod()
|
|
{
|
|
var executor = GetExecutorForMethod("VoidValueMethod");
|
|
var result = executor.Execute(
|
|
_targetObject,
|
|
new object[] { 10 });
|
|
Assert.False(executor.IsMethodAsync);
|
|
Assert.Null(result);
|
|
}
|
|
|
|
[Fact]
|
|
public void ExecuteValueMethodWithReturnType()
|
|
{
|
|
var executor = GetExecutorForMethod("ValueMethodWithReturnType");
|
|
var result = executor.Execute(
|
|
_targetObject,
|
|
new object[] { 10 });
|
|
var resultObject = Assert.IsType<TestObject>(result);
|
|
Assert.False(executor.IsMethodAsync);
|
|
Assert.Equal("Hello", resultObject.value);
|
|
}
|
|
|
|
[Fact]
|
|
public void ExecuteValueMethodUpdateValue()
|
|
{
|
|
var executor = GetExecutorForMethod("ValueMethodUpdateValue");
|
|
var parameter = new TestObject();
|
|
var result = executor.Execute(
|
|
_targetObject,
|
|
new object[] { parameter });
|
|
var resultObject = Assert.IsType<TestObject>(result);
|
|
Assert.False(executor.IsMethodAsync);
|
|
Assert.Equal("HelloWorld", resultObject.value);
|
|
}
|
|
|
|
[Fact]
|
|
public void ExecuteValueMethodWithReturnTypeThrowsException()
|
|
{
|
|
var executor = GetExecutorForMethod("ValueMethodWithReturnTypeThrowsException");
|
|
var parameter = new TestObject();
|
|
Assert.False(executor.IsMethodAsync);
|
|
Assert.Throws<NotImplementedException>(
|
|
() => executor.Execute(
|
|
_targetObject,
|
|
new object[] { parameter }));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ExecuteValueMethodAsync()
|
|
{
|
|
var executor = GetExecutorForMethod("ValueMethodAsync");
|
|
var result = await executor.ExecuteAsync(
|
|
_targetObject,
|
|
new object[] { 10, 20 });
|
|
Assert.True(executor.IsMethodAsync);
|
|
Assert.Equal(30, (int)result);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ExecuteValueMethodWithReturnTypeAsync()
|
|
{
|
|
var executor = GetExecutorForMethod("ValueMethodWithReturnTypeAsync");
|
|
var result = await executor.ExecuteAsync(
|
|
_targetObject,
|
|
new object[] { 10 });
|
|
var resultObject = Assert.IsType<TestObject>(result);
|
|
Assert.True(executor.IsMethodAsync);
|
|
Assert.Equal("Hello", resultObject.value);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ExecuteValueMethodUpdateValueAsync()
|
|
{
|
|
var executor = GetExecutorForMethod("ValueMethodUpdateValueAsync");
|
|
var parameter = new TestObject();
|
|
var result = await executor.ExecuteAsync(
|
|
_targetObject,
|
|
new object[] { parameter });
|
|
var resultObject = Assert.IsType<TestObject>(result);
|
|
Assert.True(executor.IsMethodAsync);
|
|
Assert.Equal("HelloWorld", resultObject.value);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ExecuteValueMethodWithReturnTypeThrowsExceptionAsync()
|
|
{
|
|
var executor = GetExecutorForMethod("ValueMethodWithReturnTypeThrowsExceptionAsync");
|
|
var parameter = new TestObject();
|
|
Assert.True(executor.IsMethodAsync);
|
|
await Assert.ThrowsAsync<NotImplementedException>(
|
|
async () => await executor.ExecuteAsync(
|
|
_targetObject,
|
|
new object[] { parameter }));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ExecuteValueMethodWithReturnVoidThrowsExceptionAsync()
|
|
{
|
|
var executor = GetExecutorForMethod("ValueMethodWithReturnVoidThrowsExceptionAsync");
|
|
var parameter = new TestObject();
|
|
Assert.True(executor.IsMethodAsync);
|
|
await Assert.ThrowsAsync<NotImplementedException>(
|
|
async () => await executor.ExecuteAsync(
|
|
_targetObject,
|
|
new object[] { parameter }));
|
|
}
|
|
|
|
[Fact]
|
|
public void GetDefaultValueForParameters_ReturnsSuppliedValues()
|
|
{
|
|
var suppliedDefaultValues = new object[] { 123, "test value" };
|
|
var executor = GetExecutorForMethod("MethodWithMultipleParameters", suppliedDefaultValues);
|
|
Assert.Equal(suppliedDefaultValues[0], executor.GetDefaultValueForParameter(0));
|
|
Assert.Equal(suppliedDefaultValues[1], executor.GetDefaultValueForParameter(1));
|
|
Assert.Throws<ArgumentOutOfRangeException>(() => executor.GetDefaultValueForParameter(2));
|
|
}
|
|
|
|
[Fact]
|
|
public void GetDefaultValueForParameters_ThrowsIfNoneWereSupplied()
|
|
{
|
|
var executor = GetExecutorForMethod("MethodWithMultipleParameters");
|
|
Assert.Throws<InvalidOperationException>(() => executor.GetDefaultValueForParameter(0));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task TargetMethodReturningCustomAwaitableOfReferenceType_CanInvokeViaExecute()
|
|
{
|
|
// Arrange
|
|
var executor = GetExecutorForMethod("CustomAwaitableOfReferenceTypeAsync");
|
|
|
|
// Act
|
|
var result = await (TestAwaitable<TestObject>)executor.Execute(_targetObject, new object[] { "Hello", 123 });
|
|
|
|
// Assert
|
|
Assert.True(executor.IsMethodAsync);
|
|
Assert.Same(typeof(TestObject), executor.AsyncResultType);
|
|
Assert.NotNull(result);
|
|
Assert.Equal("Hello 123", result.value);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task TargetMethodReturningCustomAwaitableOfValueType_CanInvokeViaExecute()
|
|
{
|
|
// Arrange
|
|
var executor = GetExecutorForMethod("CustomAwaitableOfValueTypeAsync");
|
|
|
|
// Act
|
|
var result = await (TestAwaitable<int>)executor.Execute(_targetObject, new object[] { 123, 456 });
|
|
|
|
// Assert
|
|
Assert.True(executor.IsMethodAsync);
|
|
Assert.Same(typeof(int), executor.AsyncResultType);
|
|
Assert.Equal(579, result);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task TargetMethodReturningCustomAwaitableOfReferenceType_CanInvokeViaExecuteAsync()
|
|
{
|
|
// Arrange
|
|
var executor = GetExecutorForMethod("CustomAwaitableOfReferenceTypeAsync");
|
|
|
|
// Act
|
|
var result = await executor.ExecuteAsync(_targetObject, new object[] { "Hello", 123 });
|
|
|
|
// Assert
|
|
Assert.True(executor.IsMethodAsync);
|
|
Assert.Same(typeof(TestObject), executor.AsyncResultType);
|
|
Assert.NotNull(result);
|
|
Assert.IsType<TestObject>(result);
|
|
Assert.Equal("Hello 123", ((TestObject)result).value);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task TargetMethodReturningCustomAwaitableOfValueType_CanInvokeViaExecuteAsync()
|
|
{
|
|
// Arrange
|
|
var executor = GetExecutorForMethod("CustomAwaitableOfValueTypeAsync");
|
|
|
|
// Act
|
|
var result = await executor.ExecuteAsync(_targetObject, new object[] { 123, 456 });
|
|
|
|
// Assert
|
|
Assert.True(executor.IsMethodAsync);
|
|
Assert.Same(typeof(int), executor.AsyncResultType);
|
|
Assert.NotNull(result);
|
|
Assert.IsType<int>(result);
|
|
Assert.Equal(579, (int)result);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task TargetMethodReturningAwaitableOfVoidType_CanInvokeViaExecuteAsync()
|
|
{
|
|
// Arrange
|
|
var executor = GetExecutorForMethod("VoidValueMethodAsync");
|
|
|
|
// Act
|
|
var result = await executor.ExecuteAsync(_targetObject, new object[] { 123 });
|
|
|
|
// Assert
|
|
Assert.True(executor.IsMethodAsync);
|
|
Assert.Same(typeof(void), executor.AsyncResultType);
|
|
Assert.Null(result);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task TargetMethodReturningAwaitableWithICriticalNotifyCompletion_UsesUnsafeOnCompleted()
|
|
{
|
|
// Arrange
|
|
var executor = GetExecutorForMethod("CustomAwaitableWithICriticalNotifyCompletion");
|
|
|
|
// Act
|
|
var result = await executor.ExecuteAsync(_targetObject, new object[0]);
|
|
|
|
// Assert
|
|
Assert.True(executor.IsMethodAsync);
|
|
Assert.Same(typeof(string), executor.AsyncResultType);
|
|
Assert.Equal("Used UnsafeOnCompleted", (string)result);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task TargetMethodReturningAwaitableWithoutICriticalNotifyCompletion_UsesOnCompleted()
|
|
{
|
|
// Arrange
|
|
var executor = GetExecutorForMethod("CustomAwaitableWithoutICriticalNotifyCompletion");
|
|
|
|
// Act
|
|
var result = await executor.ExecuteAsync(_targetObject, new object[0]);
|
|
|
|
// Assert
|
|
Assert.True(executor.IsMethodAsync);
|
|
Assert.Same(typeof(string), executor.AsyncResultType);
|
|
Assert.Equal("Used OnCompleted", (string)result);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task TargetMethodReturningValueTaskOfValueType_CanBeInvokedViaExecute()
|
|
{
|
|
// Arrange
|
|
var executor = GetExecutorForMethod("ValueTaskOfValueType");
|
|
|
|
// Act
|
|
var result = await (ValueTask<int>)executor.Execute(_targetObject, new object[] { 123 });
|
|
|
|
// Assert
|
|
Assert.True(executor.IsMethodAsync);
|
|
Assert.Same(typeof(int), executor.AsyncResultType);
|
|
Assert.Equal(123, result);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task TargetMethodReturningValueTaskOfReferenceType_CanBeInvokedViaExecute()
|
|
{
|
|
// Arrange
|
|
var executor = GetExecutorForMethod("ValueTaskOfReferenceType");
|
|
|
|
// Act
|
|
var result = await (ValueTask<string>)executor.Execute(_targetObject, new object[] { "test result" });
|
|
|
|
// Assert
|
|
Assert.True(executor.IsMethodAsync);
|
|
Assert.Same(typeof(string), executor.AsyncResultType);
|
|
Assert.Equal("test result", result);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task TargetMethodReturningValueTaskOfValueType_CanBeInvokedViaExecuteAsync()
|
|
{
|
|
// Arrange
|
|
var executor = GetExecutorForMethod("ValueTaskOfValueType");
|
|
|
|
// Act
|
|
var result = await executor.ExecuteAsync(_targetObject, new object[] { 123 });
|
|
|
|
// Assert
|
|
Assert.True(executor.IsMethodAsync);
|
|
Assert.Same(typeof(int), executor.AsyncResultType);
|
|
Assert.NotNull(result);
|
|
Assert.Equal(123, (int)result);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task TargetMethodReturningValueTaskOfReferenceType_CanBeInvokedViaExecuteAsync()
|
|
{
|
|
// Arrange
|
|
var executor = GetExecutorForMethod("ValueTaskOfReferenceType");
|
|
|
|
// Act
|
|
var result = await executor.ExecuteAsync(_targetObject, new object[] { "test result" });
|
|
|
|
// Assert
|
|
Assert.True(executor.IsMethodAsync);
|
|
Assert.Same(typeof(string), executor.AsyncResultType);
|
|
Assert.Equal("test result", result);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task TargetMethodReturningFSharpAsync_CanBeInvokedViaExecute()
|
|
{
|
|
// Arrange
|
|
var executor = GetExecutorForMethod("FSharpAsyncMethod");
|
|
|
|
// Act
|
|
var fsharpAsync = (FSharpAsync<string>)executor.Execute(_targetObject, new object[] { "test result" });
|
|
var result = await FSharpAsync.StartAsTask(fsharpAsync,
|
|
FSharpOption<TaskCreationOptions>.None,
|
|
FSharpOption<CancellationToken>.None);
|
|
|
|
// Assert
|
|
Assert.True(executor.IsMethodAsync);
|
|
Assert.Same(typeof(string), executor.AsyncResultType);
|
|
Assert.Equal("test result", result);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task TargetMethodReturningFailingFSharpAsync_CanBeInvokedViaExecute()
|
|
{
|
|
// Arrange
|
|
var executor = GetExecutorForMethod("FSharpAsyncFailureMethod");
|
|
|
|
// Act
|
|
var fsharpAsync = (FSharpAsync<string>)executor.Execute(_targetObject, new object[] { "test result" });
|
|
var resultTask = FSharpAsync.StartAsTask(fsharpAsync,
|
|
FSharpOption<TaskCreationOptions>.None,
|
|
FSharpOption<CancellationToken>.None);
|
|
|
|
// Assert
|
|
Assert.True(executor.IsMethodAsync);
|
|
Assert.Same(typeof(string), executor.AsyncResultType);
|
|
|
|
var exception = await Assert.ThrowsAsync<AggregateException>(async () => await resultTask);
|
|
Assert.IsType<InvalidOperationException>(exception.InnerException);
|
|
Assert.Equal("Test exception", exception.InnerException.Message);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task TargetMethodReturningFSharpAsync_CanBeInvokedViaExecuteAsync()
|
|
{
|
|
// Arrange
|
|
var executor = GetExecutorForMethod("FSharpAsyncMethod");
|
|
|
|
// Act
|
|
var result = await executor.ExecuteAsync(_targetObject, new object[] { "test result" });
|
|
|
|
// Assert
|
|
Assert.True(executor.IsMethodAsync);
|
|
Assert.Same(typeof(string), executor.AsyncResultType);
|
|
Assert.Equal("test result", result);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task TargetMethodReturningFailingFSharpAsync_CanBeInvokedViaExecuteAsync()
|
|
{
|
|
// Arrange
|
|
var executor = GetExecutorForMethod("FSharpAsyncFailureMethod");
|
|
|
|
// Act
|
|
var resultTask = executor.ExecuteAsync(_targetObject, new object[] { "test result" });
|
|
|
|
// Assert
|
|
Assert.True(executor.IsMethodAsync);
|
|
Assert.Same(typeof(string), executor.AsyncResultType);
|
|
|
|
var exception = await Assert.ThrowsAsync<AggregateException>(async () => await resultTask);
|
|
Assert.IsType<InvalidOperationException>(exception.InnerException);
|
|
Assert.Equal("Test exception", exception.InnerException.Message);
|
|
}
|
|
|
|
private ObjectMethodExecutor GetExecutorForMethod(string methodName)
|
|
{
|
|
var method = typeof(TestObject).GetMethod(methodName);
|
|
return ObjectMethodExecutor.Create(method, targetTypeInfo);
|
|
}
|
|
|
|
private ObjectMethodExecutor GetExecutorForMethod(string methodName, object[] parameterDefaultValues)
|
|
{
|
|
var method = typeof(TestObject).GetMethod(methodName);
|
|
return ObjectMethodExecutor.Create(method, targetTypeInfo, parameterDefaultValues);
|
|
}
|
|
|
|
public class TestObject
|
|
{
|
|
public string value;
|
|
public int ValueMethod(int i, int j)
|
|
{
|
|
return i + j;
|
|
}
|
|
|
|
public void VoidValueMethod(int i)
|
|
{
|
|
|
|
}
|
|
|
|
public TestObject ValueMethodWithReturnType(int i)
|
|
{
|
|
return new TestObject() { value = "Hello" }; ;
|
|
}
|
|
|
|
public TestObject ValueMethodWithReturnTypeThrowsException(TestObject i)
|
|
{
|
|
throw new NotImplementedException("Not Implemented Exception");
|
|
}
|
|
|
|
public TestObject ValueMethodUpdateValue(TestObject parameter)
|
|
{
|
|
parameter.value = "HelloWorld";
|
|
return parameter;
|
|
}
|
|
|
|
public Task<int> ValueMethodAsync(int i, int j)
|
|
{
|
|
return Task.FromResult<int>(i + j);
|
|
}
|
|
|
|
public async Task VoidValueMethodAsync(int i)
|
|
{
|
|
await ValueMethodAsync(3, 4);
|
|
}
|
|
public Task<TestObject> ValueMethodWithReturnTypeAsync(int i)
|
|
{
|
|
return Task.FromResult<TestObject>(new TestObject() { value = "Hello" });
|
|
}
|
|
|
|
public async Task ValueMethodWithReturnVoidThrowsExceptionAsync(TestObject i)
|
|
{
|
|
await Task.CompletedTask;
|
|
throw new NotImplementedException("Not Implemented Exception");
|
|
}
|
|
|
|
public async Task<TestObject> ValueMethodWithReturnTypeThrowsExceptionAsync(TestObject i)
|
|
{
|
|
await Task.CompletedTask;
|
|
throw new NotImplementedException("Not Implemented Exception");
|
|
}
|
|
|
|
public Task<TestObject> ValueMethodUpdateValueAsync(TestObject parameter)
|
|
{
|
|
parameter.value = "HelloWorld";
|
|
return Task.FromResult<TestObject>(parameter);
|
|
}
|
|
|
|
public TestAwaitable<TestObject> CustomAwaitableOfReferenceTypeAsync(
|
|
string input1,
|
|
int input2)
|
|
{
|
|
return new TestAwaitable<TestObject>(new TestObject
|
|
{
|
|
value = $"{input1} {input2}"
|
|
});
|
|
}
|
|
|
|
public TestAwaitable<int> CustomAwaitableOfValueTypeAsync(
|
|
int input1,
|
|
int input2)
|
|
{
|
|
return new TestAwaitable<int>(input1 + input2);
|
|
}
|
|
|
|
public TestAwaitableWithICriticalNotifyCompletion CustomAwaitableWithICriticalNotifyCompletion()
|
|
{
|
|
return new TestAwaitableWithICriticalNotifyCompletion();
|
|
}
|
|
|
|
public TestAwaitableWithoutICriticalNotifyCompletion CustomAwaitableWithoutICriticalNotifyCompletion()
|
|
{
|
|
return new TestAwaitableWithoutICriticalNotifyCompletion();
|
|
}
|
|
|
|
public ValueTask<int> ValueTaskOfValueType(int result)
|
|
{
|
|
return new ValueTask<int>(result);
|
|
}
|
|
|
|
public ValueTask<string> ValueTaskOfReferenceType(string result)
|
|
{
|
|
return new ValueTask<string>(result);
|
|
}
|
|
|
|
public void MethodWithMultipleParameters(int valueTypeParam, string referenceTypeParam)
|
|
{
|
|
}
|
|
|
|
public FSharpAsync<string> FSharpAsyncMethod(string parameter)
|
|
{
|
|
return FSharpAsync.AwaitTask(Task.FromResult(parameter));
|
|
}
|
|
|
|
public FSharpAsync<string> FSharpAsyncFailureMethod(string parameter)
|
|
{
|
|
return FSharpAsync.AwaitTask(
|
|
Task.FromException<string>(new InvalidOperationException("Test exception")));
|
|
}
|
|
}
|
|
|
|
public class TestAwaitable<T>
|
|
{
|
|
private T _result;
|
|
private bool _isCompleted;
|
|
private List<Action> _onCompletedCallbacks = new List<Action>();
|
|
|
|
public TestAwaitable(T result)
|
|
{
|
|
_result = result;
|
|
|
|
// Simulate a brief delay before completion
|
|
ThreadPool.QueueUserWorkItem(_ =>
|
|
{
|
|
Thread.Sleep(100);
|
|
SetCompleted();
|
|
});
|
|
}
|
|
|
|
private void SetCompleted()
|
|
{
|
|
_isCompleted = true;
|
|
|
|
foreach (var callback in _onCompletedCallbacks)
|
|
{
|
|
callback();
|
|
}
|
|
}
|
|
|
|
public TestAwaiter GetAwaiter()
|
|
{
|
|
return new TestAwaiter(this);
|
|
}
|
|
|
|
public struct TestAwaiter : INotifyCompletion
|
|
{
|
|
private TestAwaitable<T> _owner;
|
|
|
|
public TestAwaiter(TestAwaitable<T> owner) : this()
|
|
{
|
|
_owner = owner;
|
|
}
|
|
|
|
public bool IsCompleted => _owner._isCompleted;
|
|
|
|
public void OnCompleted(Action continuation)
|
|
{
|
|
if (_owner._isCompleted)
|
|
{
|
|
continuation();
|
|
}
|
|
else
|
|
{
|
|
_owner._onCompletedCallbacks.Add(continuation);
|
|
}
|
|
}
|
|
|
|
public T GetResult()
|
|
{
|
|
return _owner._result;
|
|
}
|
|
}
|
|
}
|
|
|
|
public class TestAwaitableWithICriticalNotifyCompletion
|
|
{
|
|
public TestAwaiterWithICriticalNotifyCompletion GetAwaiter()
|
|
=> new TestAwaiterWithICriticalNotifyCompletion();
|
|
}
|
|
|
|
public class TestAwaitableWithoutICriticalNotifyCompletion
|
|
{
|
|
public TestAwaiterWithoutICriticalNotifyCompletion GetAwaiter()
|
|
=> new TestAwaiterWithoutICriticalNotifyCompletion();
|
|
}
|
|
|
|
public class TestAwaiterWithICriticalNotifyCompletion
|
|
: CompletionTrackingAwaiterBase, ICriticalNotifyCompletion
|
|
{
|
|
}
|
|
|
|
public class TestAwaiterWithoutICriticalNotifyCompletion
|
|
: CompletionTrackingAwaiterBase, INotifyCompletion
|
|
{
|
|
}
|
|
|
|
public class CompletionTrackingAwaiterBase
|
|
{
|
|
private string _result;
|
|
|
|
public bool IsCompleted { get; private set; }
|
|
|
|
public string GetResult() => _result;
|
|
|
|
public void OnCompleted(Action continuation)
|
|
{
|
|
_result = "Used OnCompleted";
|
|
IsCompleted = true;
|
|
continuation();
|
|
}
|
|
|
|
public void UnsafeOnCompleted(Action continuation)
|
|
{
|
|
_result = "Used UnsafeOnCompleted";
|
|
IsCompleted = true;
|
|
continuation();
|
|
}
|
|
}
|
|
}
|
|
}
|