1 // ***********************************************************************
2 // Copyright (c) 2013 Charlie Poole
4 // Permission is hereby granted, free of charge, to any person obtaining
5 // a copy of this software and associated documentation files (the
6 // "Software"), to deal in the Software without restriction, including
7 // without limitation the rights to use, copy, modify, merge, publish,
8 // distribute, sublicense, and/or sell copies of the Software, and to
9 // permit persons to whom the Software is furnished to do so, subject to
10 // the following conditions:
12 // The above copyright notice and this permission notice shall be
13 // included in all copies or substantial portions of the Software.
15 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 // ***********************************************************************
25 #define NUNIT_FRAMEWORK
29 #if NET_4_0 || NET_4_5 || PORTABLE
31 using System.Collections.Generic;
33 using System.Reflection;
34 using System.Threading;
35 using NUnit.Compatibility;
37 #if NET_4_5 || PORTABLE
38 using System.Runtime.ExceptionServices;
41 namespace NUnit.Framework.Internal
43 internal abstract class AsyncInvocationRegion : IDisposable
45 private const string TaskTypeName = "System.Threading.Tasks.Task";
46 private const string AsyncAttributeTypeName = "System.Runtime.CompilerServices.AsyncStateMachineAttribute";
48 private AsyncInvocationRegion()
52 public static AsyncInvocationRegion Create(Delegate @delegate)
55 return Create(@delegate.GetMethodInfo());
57 return Create(@delegate.Method);
61 public static AsyncInvocationRegion Create(MethodInfo method)
63 if (!IsAsyncOperation(method))
64 throw new ArgumentException(@"Either asynchronous support is not available or an attempt
65 at wrapping a non-async method invocation in an async region was done");
67 if (method.ReturnType == typeof(void))
68 throw new ArgumentException("'async void' methods are not supported, please use 'async Task' instead");
70 return new AsyncTaskInvocationRegion();
73 public static bool IsAsyncOperation(MethodInfo method)
75 var name = method.ReturnType.FullName;
76 if (name == null) return false;
77 return name.StartsWith(TaskTypeName) ||
78 method.GetCustomAttributes(false).Any(attr => AsyncAttributeTypeName == attr.GetType().FullName);
81 public static bool IsAsyncOperation(Delegate @delegate)
84 return IsAsyncOperation(@delegate.GetMethodInfo());
86 return IsAsyncOperation(@delegate.Method);
91 /// Waits for pending asynchronous operations to complete, if appropriate,
92 /// and returns a proper result of the invocation by unwrapping task results
94 /// <param name="invocationResult">The raw result of the method invocation</param>
95 /// <returns>The unwrapped result, if necessary</returns>
96 public abstract object WaitForPendingOperationsToComplete(object invocationResult);
98 public virtual void Dispose()
101 private class AsyncTaskInvocationRegion : AsyncInvocationRegion
103 private const string TaskWaitMethod = "Wait";
104 private const string TaskResultProperty = "Result";
105 private const string VoidTaskResultType = "VoidTaskResult";
106 private const string SystemAggregateException = "System.AggregateException";
107 private const string InnerExceptionsProperty = "InnerExceptions";
108 private const BindingFlags TaskResultPropertyBindingFlags = BindingFlags.Instance | BindingFlags.Public;
110 public override object WaitForPendingOperationsToComplete(object invocationResult)
114 invocationResult.GetType().GetMethod(TaskWaitMethod, new Type[0]).Invoke(invocationResult, null);
116 catch (TargetInvocationException e)
118 IList<Exception> innerExceptions = GetAllExceptions(e.InnerException);
119 ExceptionHelper.Rethrow(innerExceptions[0]);
121 var args = invocationResult.GetType().GetGenericArguments();
122 if (args != null && args.Length == 1 && args[0].Name == VoidTaskResultType)
127 PropertyInfo taskResultProperty = invocationResult.GetType().GetProperty(TaskResultProperty, TaskResultPropertyBindingFlags);
129 return taskResultProperty != null ? taskResultProperty.GetValue(invocationResult, null) : invocationResult;
132 private static IList<Exception> GetAllExceptions(Exception exception)
134 if (SystemAggregateException.Equals(exception.GetType().FullName))
135 return (IList<Exception>)exception.GetType().GetProperty(InnerExceptionsProperty).GetValue(exception, null);
137 return new Exception[] { exception };