< Summary

Class:Azure.Core.TestFramework.UseSyncMethodsInterceptor
Assembly:Azure.Core.TestFramework
File(s):C:\Git\azure-sdk-for-net\sdk\core\Azure.Core.TestFramework\src\UseSyncMethodsInterceptor.cs
Covered lines:100
Uncovered lines:4
Coverable lines:104
Total lines:270
Line coverage:96.1% (100 of 104)
Covered branches:48
Total branches:56
Branch coverage:85.7% (48 of 56)

Metrics

MethodCyclomatic complexity Line coverage Branch coverage
.ctor(...)-100%100%
Intercept(...)-100%100%
SetAsyncResult(...)-90%66.67%
SetAsyncException(...)-90.91%50%
CloseResponseType(...)-66.67%83.33%
GetMethod(...)-100%100%
IsInternal(...)-100%75%
.ctor(...)-100%100%
AsPages()-100%87.5%

File(s)

C:\Git\azure-sdk-for-net\sdk\core\Azure.Core.TestFramework\src\UseSyncMethodsInterceptor.cs

#LineLine coverage
 1// Copyright (c) Microsoft Corporation. All rights reserved.
 2// Licensed under the MIT License.
 3
 4using System;
 5using System.Collections.Generic;
 6using System.Diagnostics;
 7using System.Linq;
 8using System.Reflection;
 9using System.Runtime.ExceptionServices;
 10using System.Threading.Tasks;
 11using Castle.DynamicProxy;
 12
 13namespace Azure.Core.TestFramework
 14{
 15    /// <summary>
 16    /// This interceptor forwards the async call to a sync method call with the same arguments
 17    /// </summary>
 18    public class UseSyncMethodsInterceptor : IInterceptor
 19    {
 20        private readonly bool _forceSync;
 21
 13222        public UseSyncMethodsInterceptor(bool forceSync)
 23        {
 13224            _forceSync = forceSync;
 13225        }
 26
 27        private const string AsyncSuffix = "Async";
 28
 13229        private readonly MethodInfo _taskFromResultMethod = typeof(Task)
 13230            .GetMethod("FromResult", BindingFlags.Static | BindingFlags.Public);
 31
 13232        private readonly MethodInfo _taskFromExceptionMethod = typeof(Task)
 13233            .GetMethods(BindingFlags.Static | BindingFlags.Public)
 528034            .Single(m => m.Name == "FromException" && m.IsGenericMethod);
 35
 36        [DebuggerStepThrough]
 37        public void Intercept(IInvocation invocation)
 38        {
 42010539            Type[] parameterTypes = invocation.Method.GetParameters().Select(p => p.ParameterType).ToArray();
 40
 16427541            var methodName = invocation.Method.Name;
 16427542            if (!methodName.EndsWith(AsyncSuffix))
 43            {
 8842644                MethodInfo asyncAlternative = GetMethod(invocation, methodName + AsyncSuffix, parameterTypes);
 45
 46                // Check if there is an async alternative to sync call
 8842647                if (asyncAlternative != null)
 48                {
 249                    throw new InvalidOperationException($"Async method call expected for {methodName}");
 50                }
 51                else
 52                {
 8842453                    invocation.Proceed();
 8842054                    return;
 55                }
 56            }
 57
 7584958            if (!_forceSync)
 59            {
 3826760                invocation.Proceed();
 3825561                return;
 62            }
 63
 3758264            var nonAsyncMethodName = methodName.Substring(0, methodName.Length - AsyncSuffix.Length);
 65
 3758266            MethodInfo methodInfo = GetMethod(invocation, nonAsyncMethodName, parameterTypes);
 3758267            if (methodInfo == null)
 68            {
 269                throw new InvalidOperationException($"Unable to find a method with name {nonAsyncMethodName} and {string
 270                                                    + "Make sure both methods have the same signature including the canc
 71            }
 72
 3758073            Type returnType = methodInfo.ReturnType;
 3758074            bool returnsSyncCollection = returnType.IsGenericType && returnType.GetGenericTypeDefinition() == typeof(Pag
 75
 76            try
 77            {
 78                // If we've got GetAsync<Model>() and just found Get<T>(), we
 79                // need to change the method to Get<Model>().
 3758080                if (methodInfo.ContainsGenericParameters)
 81                {
 46282                    methodInfo = methodInfo.MakeGenericMethod(invocation.Method.GetGenericArguments());
 46283                    returnType = methodInfo.ReturnType;
 84                }
 3758085                object result = methodInfo.Invoke(invocation.InvocationTarget, invocation.Arguments);
 86
 87                // Map IEnumerable to IAsyncEnumerable
 3570988                if (returnsSyncCollection)
 89                {
 162390                    Type[] modelType = returnType.GenericTypeArguments;
 162391                    Type wrapperType = typeof(SyncPageableWrapper<>).MakeGenericType(modelType);
 92
 162393                    invocation.ReturnValue = Activator.CreateInstance(wrapperType, new[] { result });
 94                }
 95                else
 96                {
 3408697                    SetAsyncResult(invocation, returnType, result);
 98                }
 3570999            }
 1871100            catch (TargetInvocationException exception)
 101            {
 1871102                if (returnsSyncCollection)
 103                {
 8104                    ExceptionDispatchInfo.Capture(exception.InnerException).Throw();
 105                }
 106                else
 107                {
 1863108                    SetAsyncException(invocation, returnType, exception.InnerException);
 109                }
 1863110            }
 37572111        }
 112
 113        private void SetAsyncResult(IInvocation invocation, Type returnType, object result)
 114        {
 34086115            Type methodReturnType = invocation.Method.ReturnType;
 34086116            if (methodReturnType.IsGenericType)
 117            {
 34086118                returnType = CloseResponseType(returnType, methodReturnType);
 34086119                if (methodReturnType.GetGenericTypeDefinition() == typeof(Task<>))
 120                {
 33978121                    invocation.ReturnValue = _taskFromResultMethod.MakeGenericMethod(returnType).Invoke(null, new[] { re
 33978122                    return;
 123                }
 108124                if (methodReturnType.GetGenericTypeDefinition() == typeof(ValueTask<>))
 125                {
 108126                    invocation.ReturnValue = Activator.CreateInstance(typeof(ValueTask<>).MakeGenericType(returnType), r
 108127                    return;
 128                }
 129            }
 130
 0131            throw new NotSupportedException();
 132        }
 133
 134        private void SetAsyncException(IInvocation invocation, Type returnType, Exception result)
 135        {
 1863136            Type methodReturnType = invocation.Method.ReturnType;
 1863137            if (methodReturnType.IsGenericType)
 138            {
 1863139                returnType = CloseResponseType(returnType, methodReturnType);
 1863140                if (methodReturnType.GetGenericTypeDefinition() == typeof(Task<>))
 141                {
 1733142                    invocation.ReturnValue = _taskFromExceptionMethod.MakeGenericMethod(returnType).Invoke(null, new[] {
 1733143                    return;
 144                }
 145
 130146                if (methodReturnType.GetGenericTypeDefinition() == typeof(ValueTask<>))
 147                {
 130148                    var task = _taskFromExceptionMethod.MakeGenericMethod(returnType).Invoke(null, new[] { result });
 130149                    invocation.ReturnValue = Activator.CreateInstance(typeof(ValueTask<>).MakeGenericType(returnType), t
 130150                    return;
 151                }
 152            }
 153
 0154            throw new NotSupportedException();
 155        }
 156
 157        /// <summary>
 158        /// If the sync method returned Response{Model} and the async method's
 159        /// return type is still an open Response{U}, we need to close it to
 160        /// Response{Model} as well.  We don't care about this if Response{}
 161        /// has already been closed.
 162        /// </summary>
 163        /// <param name="returnType">The sync method's return type.</param>
 164        /// <param name="methodReturnType">The async method's return type.</param>
 165        /// <returns>A return type with Response{U} closed.</returns>
 166        private static Type CloseResponseType(Type returnType, Type methodReturnType)
 167        {
 35949168            if (returnType.IsGenericType &&
 35949169                returnType.GetGenericTypeDefinition() == typeof(Response<>) &&
 35949170                returnType.ContainsGenericParameters)
 171            {
 0172                Type modelType = methodReturnType.GetGenericArguments()[0].GetGenericArguments()[0];
 0173                returnType = returnType.GetGenericTypeDefinition().MakeGenericType(modelType);
 174            }
 35949175            return returnType;
 176        }
 177
 178        private static MethodInfo GetMethod(IInvocation invocation, string nonAsyncMethodName, Type[] types)
 179        {
 126008180            BindingFlags flags = IsInternal(invocation.Method) ?
 126008181                BindingFlags.Instance | BindingFlags.NonPublic :
 126008182                BindingFlags.Instance | BindingFlags.Public;
 183
 184            // Do our own slow "lightweight binding" in situations where we
 185            // have generic arguments that aren't factored into the binder for
 186            // the regular GetMethod call.  We're taking lots of shortcuts like
 187            // only comparing the generic type or count and it's only enough
 188            // for the cases we have today.
 5970189            static Type GenericDef(Type t) => t.IsGenericType ? t.GetGenericTypeDefinition() : t;
 190            MethodInfo GetMethodSlow()
 191            {
 89646192                var methods = invocation.TargetType
 89646193                // Start with all methods that have the right name
 6002284194                .GetMethods(flags).Where(m => m.Name == nonAsyncMethodName);
 195
 196                // Check if their type parameters have the same generic
 197                // type definitions (i.e., if I invoked
 198                // GetAsync<Model>(Wrapper<Model>) we want that to match
 199                // with Get<T>(Wrapper<T>)
 89646200                var genericDefs = methods.Where(m =>
 201                    m.GetParameters().Select(p => GenericDef(p.ParameterType))
 3458202                    .SequenceEqual(types.Select(GenericDef)));
 203
 204                // If the previous check has any results, check if they have the same number of type arguments
 205                // (all of our cases today either specialize on 0 or 1 type
 206                // argument for the static or dynamic user schema approach)
 207                // Else, close each GenericMethodDefinition and compare its paramter types.
 89646208                var withSimilarGenericArguments = genericDefs.Any() ?
 89646209                    genericDefs.Where(m =>
 2033210                        m.GetGenericArguments().Length ==
 2033211                        invocation.Method.GetGenericArguments().Length) :
 89646212                    methods
 213                        .Where(m => m.IsGenericMethodDefinition)
 2632214                        .Select(m => m.MakeGenericMethod(invocation.GenericArguments))
 215                        .Where(gm => gm.GetParameters().Select(p => p.ParameterType)
 216                            .SequenceEqual(invocation.Method.GetParameters().Select(p => p.ParameterType)));
 217
 218                // Hopefully we're down to 1.  If you arrive here in the
 219                // future because SingleOrDefault threw, we need to make
 220                // the comparison logic more specific.  If you arrive here
 221                // because we're returning null, then we need to search
 222                // a little more broadly.  Either way, congratulations on
 223                // blazing new API patterns and taking us boldly into the
 224                // future.
 89646225                return withSimilarGenericArguments.SingleOrDefault();
 226            }
 227
 228            try
 229            {
 126008230                return invocation.TargetType.GetMethod(
 126008231                    nonAsyncMethodName,
 126008232                    flags,
 126008233                    null,
 126008234                    types,
 126008235                    null) ??
 126008236                    // Search a little more broadly if the regular binder
 126008237                    // couldn't find a match
 126008238                    GetMethodSlow();
 239            }
 8240            catch (AmbiguousMatchException)
 241            {
 242                // Use our own binder to pick the best method if the regular
 243                // binder couldn't decide between multiple choices
 8244                return GetMethodSlow();
 245            }
 126008246        }
 247
 126008248        private static bool IsInternal(MethodBase method) => method.IsAssembly || method.IsFamilyAndAssembly && !method.
 249
 250        private class SyncPageableWrapper<T> : AsyncPageable<T>
 251        {
 252            private readonly Pageable<T> _enumerable;
 253
 1623254            public SyncPageableWrapper(Pageable<T> enumerable)
 255            {
 1623256                _enumerable = enumerable;
 1623257            }
 258
 259#pragma warning disable 1998
 260            public override async IAsyncEnumerable<Page<T>> AsPages(string continuationToken = default, int? pageSizeHin
 261#pragma warning restore 1998
 262            {
 6564263                foreach (Page<T> page in _enumerable.AsPages())
 264                {
 1682265                    yield return page;
 266                }
 1598267            }
 268        }
 269    }
 270}