Exemplo n.º 1
0
        /// <inheritdoc/>
        protected internal sealed override object GetDefaultValue(Type type, Mock mock)
        {
            Debug.Assert(type != null);
            Debug.Assert(type != typeof(void));
            Debug.Assert(mock != null);

            var handlerKey = type.IsGenericType
                ? type.GetGenericTypeDefinition()
                : type.IsArray
                    ? typeof(Array)
                    : type;

            Func <Type, Mock, object> factory;

            if (
                this.factories.TryGetValue(handlerKey, out factory) ||
                this.factories.TryGetValue(handlerKey.FullName, out factory)
                )
            {
                if (factory != null) // This prevents delegation to an `IAwaitableFactory` for deregistered awaitable types; see note above.
                {
                    return(factory.Invoke(type, mock));
                }
            }
            else if (AwaitableFactory.TryGet(type) is { } awaitableFactory)
            {
                var resultType = awaitableFactory.ResultType;
                var result     =
                    resultType != typeof(void) ? this.GetDefaultValue(resultType, mock) : null;
                return(awaitableFactory.CreateCompleted(result));
            }

            return(this.GetFallbackDefaultValue(type, mock));
        }
 public MonitorDataExchangeTests()
 {
     _awaitableFactory = new AwaitableFactory();
     _monitorRegistry  = new HealthMonitorRegistry(new[] { Mock.Of <IHealthMonitor>(cfg => cfg.Name == MonitorTypeName) });
     _exchangeClient   = new Mock <IHealthMonitorExchangeClient>();
     _endpointRegistry = new Mock <IMonitorableEndpointRegistry>();
 }
Exemplo n.º 3
0
            public void Intercept(Invocation invocation)
            {
                var returnType = invocation.Method.ReturnType;

                // In theory, each recorder should receive exactly one invocation.
                // There are some reasons why that may not always be true:
                //
                //  1. You may be inspecting a `Recorder` object in your IDE, causing
                //     additional calls e.g. to `ToString`. In this case, any such
                //     subsequent calls should be ignored.
                //
                //  2. The proxied type may perform virtual calls in its own ctor.
                //     In this case, *only* the last call is going to be relevant.
                //
                // Getting (2) right is more important than getting (1) right, so we
                // disable the following guard and allow subsequent calls to override
                // earlier ones:

                //if (this.invocation == null)
                {
                    this.invocation          = invocation;
                    this.invocationTimestamp = this.matcherObserver.GetNextTimestamp();

                    if (returnType == typeof(void))
                    {
                        this.returnValue = null;
                    }
                    else if (AwaitableFactory.TryGet(returnType) is { } awaitableFactory)
                    {
                        var result = CreateProxy(awaitableFactory.ResultType, null, this.matcherObserver, out _);
                        this.returnValue = awaitableFactory.CreateCompleted(result);
                    }
Exemplo n.º 4
0
        /// <summary>
        ///   Recursively gets the result of (i.e. "unwraps") completed awaitables
        ///   until a value is found that isn't a successfully completed awaitable.
        /// </summary>
        /// <remarks>
        ///   As an example, given <paramref name="obj"/> := <c>Task.FromResult(Task.FromResult(42))</c>,
        ///   this method will return <c>42</c>.
        /// </remarks>
        /// <param name="obj">The (possibly awaitable) object to be "unwrapped".</param>
        public static object TryGetResultRecursive(object obj)
        {
            if (obj != null &&
                AwaitableFactory.TryGet(obj.GetType()) is { } awaitableFactory &&
                awaitableFactory.TryGetResult(obj, out var result))
            {
                return(result);
            }

            return(obj);
        }
        public HealthSamplerTests()
        {
            var settings = SetupSettings();
            _healthUpdateListener = new Mock<IEndpointHealthUpdateListener>();
            _timeCoordinator = new Mock<ITimeCoordinator>();
            _timeCoordinator.Setup(c => c.UtcNow).Returns(_utcNow);

            _sampler = new HealthSampler(settings.Object, _healthUpdateListener.Object, _timeCoordinator.Object);
            _monitor = new Mock<IHealthMonitor>();
            _monitor.Setup(m => m.Name).Returns("monitor");

            _endpoint = new MonitorableEndpoint(new EndpointIdentity(Guid.NewGuid(), "monitor", "address"), _monitor.Object);
            _awaitableFactory = new AwaitableFactory();
        }
        public HealthSamplerTests()
        {
            var settings = SetupSettings();

            _healthUpdateListener = new Mock <IEndpointHealthUpdateListener>();
            _timeCoordinator      = new Mock <ITimeCoordinator>();
            _timeCoordinator.Setup(c => c.UtcNow).Returns(_utcNow);

            _sampler = new HealthSampler(settings.Object, _healthUpdateListener.Object, _timeCoordinator.Object);
            _monitor = new Mock <IHealthMonitor>();
            _monitor.Setup(m => m.Name).Returns("monitor");

            _endpoint         = new MonitorableEndpoint(new EndpointIdentity(Guid.NewGuid(), "monitor", "address"), _monitor.Object);
            _awaitableFactory = new AwaitableFactory();
        }
Exemplo n.º 7
0
        public override Expression <Action <T> > ReconstructExpression <T>(
            Action <T> action,
            object[] ctorArgs = null
            )
        {
            using (var matcherObserver = MatcherObserver.Activate())
            {
                // Create the root recording proxy:
                var root = (T)CreateProxy(
                    typeof(T),
                    ctorArgs,
                    matcherObserver,
                    out var rootRecorder
                    );

                Exception error = null;
                try
                {
                    // Execute the delegate. The root recorder will automatically "mock" return values
                    // and so build a chain of recorders, whereby each one records a single invocation
                    // in a method chain `o.X.Y.Z`:
                    action.Invoke(root);
                }
                catch (Exception ex)
                {
                    // Something went wrong. We don't return this error right away. We want to
                    // rebuild the expression tree as far as possible for diagnostic purposes.
                    error = ex;
                }

                // Start the expression tree with a parameter of type `T`:
                var        actionParameters    = action.GetMethodInfo().GetParameters();
                var        actionParameterName = actionParameters[actionParameters.Length - 1].Name;
                var        rootExpression      = Expression.Parameter(typeof(T), actionParameterName);
                Expression body = rootExpression;

                // Then step through one recorded invocation at a time:
                for (var recorder = rootRecorder; recorder != null; recorder = recorder.Next)
                {
                    var invocation = recorder.Invocation;
                    if (invocation != null)
                    {
                        var resultType = invocation.Method.DeclaringType;
                        if (resultType.IsAssignableFrom(body.Type) == false)
                        {
                            if (
                                AwaitableFactory.TryGet(body.Type) is { } awaitableHandler &&
                                awaitableHandler.ResultType.IsAssignableFrom(resultType)
                                )
                            {
                                // We are here because the current invocation cannot be chained onto the previous one,
                                // however it *can* be chained if we assume that there was a `.Result` query on the
                                // former invocation that we don't see because non-virtual members aren't recorded.
                                // In this case, we make things work by adding back the missing `.Result`:
                                body = awaitableHandler.CreateResultExpression(body);
                            }
                        }
                        body = Expression.Call(
                            body,
                            invocation.Method,
                            GetArgumentExpressions(invocation, recorder.Matches.ToArray())
                            );
                    }
                    else
                    {
                        // A recorder was set up, but it recorded no invocation. This means
                        // that the invocation could not be intercepted:
                        throw new ArgumentException(
                                  string.Format(
                                      CultureInfo.CurrentCulture,
                                      Resources.UnsupportedExpressionWithHint,
                                      $"{actionParameterName} => {body.ToStringFixed()}...",
                                      Resources.NextMemberNonInterceptable
                                      )
                                  );
                    }
                }

                // Now we've either got no error and a completely reconstructed expression, or
                // we have an error and a partially reconstructed expression which we can use for
                // diagnostic purposes:
                if (error == null)
                {
                    return(Expression.Lambda <Action <T> >(
                               body.Apply(UpgradePropertyAccessorMethods.Rewriter),
                               rootExpression
                               ));
                }
                else
                {
                    throw new ArgumentException(
                              string.Format(
                                  CultureInfo.CurrentCulture,
                                  Resources.UnsupportedExpressionWithHint,
                                  $"{actionParameterName} => {body.ToStringFixed()}...",
                                  error.Message
                                  )
                              );
                }
            }

            Expression[] GetArgumentExpressions(Invocation invocation, Match[] matches)
            {
                // First, let's pretend that all arguments are constant values:
                var parameterTypes = invocation.Method.GetParameterTypes();
                var parameterCount = parameterTypes.Count;
                var expressions    = new Expression[parameterCount];

                for (int i = 0; i < parameterCount; ++i)
                {
                    expressions[i] = Expression.Constant(
                        invocation.Arguments[i],
                        parameterTypes[i]
                        );
                }

                // Now let's override the above constant expressions with argument matchers, if available:
                if (matches.Length > 0)
                {
                    int matchIndex = 0;
                    for (
                        int argumentIndex = 0;
                        matchIndex < matches.Length && argumentIndex < expressions.Length;
                        ++argumentIndex
                        )
                    {
                        // We are assuming that by default matchers return `default(T)`. If a matcher was used,
                        // it will have left behind a `default(T)` argument, possibly coerced to the parameter type.
                        // Therefore, we attempt to reproduce such coercions using `Convert.ChangeType`:
                        Type   defaultValueType = matches[matchIndex].RenderExpression.Type;
                        object defaultValue     = defaultValueType.GetDefaultValue();
                        try
                        {
                            defaultValue = Convert.ChangeType(
                                defaultValue,
                                parameterTypes[argumentIndex]
                                );
                        }
                        catch
                        {
                            // Never mind, we tried.
                        }

                        if (!object.Equals(invocation.Arguments[argumentIndex], defaultValue))
                        {
                            // This parameter has a non-`default` value.  We therefore assume that it isn't
                            // a value that was produced by a matcher. (See explanation in comment above.)
                            continue;
                        }

                        if (
                            parameterTypes[argumentIndex].IsAssignableFrom(
                                defaultValue?.GetType() ?? defaultValueType
                                )
                            )
                        {
                            // We found a potential match. (Matcher type is assignment-compatible to parameter type.)

                            if (
                                matchIndex < matches.Length - 1 &&
                                !(
                                    argumentIndex < expressions.Length - 1 ||
                                    CanDistribute(matchIndex + 1, argumentIndex + 1)
                                    )
                                )
                            {
                                // We get here if there are more matchers to distribute,
                                // but we either:
                                //  * ran out of parameters to distribute over, or
                                //  * the remaining matchers can't be distributed over the remaining parameters.
                                // In this case, we bail out, which will lead to an exception being thrown.
                                break;
                            }

                            // The remaining matchers can be distributed over the remaining parameters,
                            // so we can use up this matcher:
                            expressions[argumentIndex] = new MatchExpression(matches[matchIndex]);
                            ++matchIndex;
                        }
                    }

                    if (matchIndex < matches.Length)
                    {
                        // If we get here, we can be almost certain that matchers weren't distributed properly
                        // across the invocation's parameters. We could hope for the best and just leave it
                        // at that; however, it's probably better to let client code know, so it can be either
                        // adjusted or reported to Moq.
                        throw new ArgumentException(
                                  string.Format(
                                      CultureInfo.CurrentCulture,
                                      Resources.MatcherAssignmentFailedDuringExpressionReconstruction,
                                      matches.Length,
                                      $"{invocation.Method.DeclaringType.GetFormattedName()}.{invocation.Method.Name}"
                                      )
                                  );
                    }

                    bool CanDistribute(int msi, int asi)
                    {
                        var match     = matches[msi];
                        var matchType = match.RenderExpression.Type;

                        for (int ai = asi; ai < expressions.Length; ++ai)
                        {
                            if (
                                parameterTypes[ai].IsAssignableFrom(matchType) &&
                                CanDistribute(msi + 1, ai + 1)
                                )
                            {
                                return(true);
                            }
                        }
                        return(false);
                    }
                }

                // Finally, add explicit type casts (aka `Convert` nodes) where necessary:
                for (int i = 0; i < expressions.Length; ++i)
                {
                    var argument      = expressions[i];
                    var parameterType = parameterTypes[i];

                    if (argument.Type == parameterType)
                    {
                        continue;
                    }

                    // nullable type coercion:
                    if (
                        Nullable.GetUnderlyingType(parameterType) != null &&
                        Nullable.GetUnderlyingType(argument.Type) == null
                        )
                    {
                        expressions[i] = Expression.Convert(argument, parameterType);
                    }
                    // boxing of value types (i.e. where a value-typed value is assigned to a reference-typed parameter):
                    else if (argument.Type.IsValueType && !parameterType.IsValueType)
                    {
                        expressions[i] = Expression.Convert(argument, parameterType);
                    }
                    // if types don't match exactly and aren't assignment compatible:
                    else if (
                        argument.Type != parameterType &&
                        !parameterType.IsAssignableFrom(argument.Type)
                        )
                    {
                        expressions[i] = Expression.Convert(argument, parameterType);
                    }
                }

                return(expressions);
            }
        }
Exemplo n.º 8
0
        /// <summary>
        ///   Splits an expression such as `<c>m => m.A.B(x).C[y] = z</c>` into a chain of parts
        ///   that can be set up one at a time:
        ///   <list>
        ///     <item>`<c>m => m.A</c>`</item>,
        ///     <item>`<c>... => ....B(x)</c>`</item>,
        ///     <item>`<c>... => ....C</c>`</item>,
        ///     <item>`<c>... => ...[y] = z</c>`</item>.
        ///   </list>
        ///   <para>
        ///     The split points are chosen such that each part has exactly one associated
        ///     <see cref="MethodInfo"/> and optionally some argument expressions.
        ///   </para>
        /// </summary>
        /// <exception cref="ArgumentException">
        ///   It was not possible to completely split up the expression.
        /// </exception>
        internal static Stack <InvocationShape> Split(this LambdaExpression expression, bool allowNonOverridableLastProperty = false)
        {
            Debug.Assert(expression != null);

            var parts = new Stack <InvocationShape>();

            Expression remainder = expression.Body;

            while (CanSplit(remainder))
            {
                Split(remainder, out remainder, out var part, allowNonOverridableLastProperty: allowNonOverridableLastProperty && parts.Count == 0);
                parts.Push(part);
            }

            if (parts.Count > 0 && remainder is ParameterExpression)
            {
                return(parts);
            }
            else
            {
                throw new ArgumentException(
                          string.Format(
                              CultureInfo.CurrentCulture,
                              Resources.UnsupportedExpression,
                              remainder.ToStringFixed()));
            }

            void Split(Expression e, out Expression r /* remainder */, out InvocationShape p /* part */, bool assignment = false, bool allowNonOverridableLastProperty = false)
            {
                const string ParameterName = "...";

                switch (e.NodeType)
                {
                case ExpressionType.Assign:                                  // assignment to a property or indexer
                case ExpressionType.AddAssign:                               // subscription of event handler to event
                case ExpressionType.SubtractAssign:                          // unsubscription of event handler from event
                {
                    var assignmentExpression = (BinaryExpression)e;
                    Split(assignmentExpression.Left, out r, out var lhs, assignment: true);
                    var parameter = Expression.Parameter(r.Type, r is ParameterExpression ope ? ope.Name : ParameterName);
                    var arguments = new Expression[lhs.Method.GetParameters().Length];
                    for (var ai = 0; ai < arguments.Length - 1; ++ai)
                    {
                        arguments[ai] = lhs.Arguments[ai];
                    }
                    arguments[arguments.Length - 1] = assignmentExpression.Right;
                    p = new InvocationShape(
                        expression: Expression.Lambda(
                            Expression.MakeBinary(e.NodeType, lhs.Expression.Body, assignmentExpression.Right),
                            parameter),
                        method: lhs.Method,
                        arguments);
                    return;
                }

                case ExpressionType.Call:                          // regular method call
                {
                    var methodCallExpression = (MethodCallExpression)e;

                    if (methodCallExpression.Method.IsGenericMethod)
                    {
                        foreach (var typeArgument in methodCallExpression.Method.GetGenericArguments())
                        {
                            if (typeArgument.IsOrContainsTypeMatcher())
                            {
                                // This is a (somewhat roundabout) way of ensuring that the type matchers used
                                // will be usable. They will not be usable if they don't implement the type
                                // matcher protocol correctly; and `SubstituteTypeMatchers` tests for that, so
                                // we'll reuse its recursive logic instead of having to reimplement our own.
                                _ = typeArgument.SubstituteTypeMatchers(typeArgument);
                            }
                        }
                    }

                    if (!methodCallExpression.Method.IsStatic)
                    {
                        r = methodCallExpression.Object;
                        var parameter = Expression.Parameter(r.Type, r is ParameterExpression ope ? ope.Name : ParameterName);
                        var method    = methodCallExpression.Method;
                        var arguments = methodCallExpression.Arguments;
                        p = new InvocationShape(
                            expression: Expression.Lambda(
                                Expression.Call(parameter, method, arguments),
                                parameter),
                            method,
                            arguments);
                    }
                    else
                    {
                        Debug.Assert(methodCallExpression.Method.IsExtensionMethod());
                        Debug.Assert(methodCallExpression.Arguments.Count > 0);
                        r = methodCallExpression.Arguments[0];
                        var parameter = Expression.Parameter(r.Type, r is ParameterExpression ope ? ope.Name : ParameterName);
                        var method    = methodCallExpression.Method;
                        var arguments = methodCallExpression.Arguments.ToArray();
                        arguments[0] = parameter;
                        p            = new InvocationShape(
                            expression: Expression.Lambda(
                                Expression.Call(method, arguments),
                                parameter),
                            method,
                            arguments);
                    }
                    return;
                }

                case ExpressionType.Index:                          // indexer query
                {
                    var indexExpression = (IndexExpression)e;
                    r = indexExpression.Object;
                    var        parameter = Expression.Parameter(r.Type, r is ParameterExpression ope ? ope.Name : ParameterName);
                    var        indexer   = indexExpression.Indexer;
                    var        arguments = indexExpression.Arguments;
                    MethodInfo method;
                    if (!assignment && indexer.CanRead(out var getter, out var getterIndexer))
                    {
                        method  = getter;
                        indexer = getterIndexer;
                    }
                    else if (indexer.CanWrite(out var setter, out var setterIndexer))
                    {
                        method  = setter;
                        indexer = setterIndexer;
                    }
                    else                              // This should be unreachable.
                    {
                        method = null;
                    }
                    p = new InvocationShape(
                        expression: Expression.Lambda(
                            Expression.MakeIndex(parameter, indexer, arguments),
                            parameter),
                        method,
                        arguments,
                        skipMatcherInitialization: assignment,
                        allowNonOverridable: allowNonOverridableLastProperty);
                    return;
                }

                case ExpressionType.Invoke:                          // delegate invocation
                {
                    var invocationExpression = (InvocationExpression)e;
                    Debug.Assert(invocationExpression.Expression.Type.IsDelegateType());
                    r = invocationExpression.Expression;
                    var parameter = Expression.Parameter(r.Type, r is ParameterExpression ope ? ope.Name : ParameterName);
                    var arguments = invocationExpression.Arguments;
                    p = new InvocationShape(
                        expression: Expression.Lambda(
                            Expression.Invoke(parameter, arguments),
                            parameter),
                        method: r.Type.GetMethod("Invoke", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance),
                        arguments);
                    return;
                }

                case ExpressionType.MemberAccess:                          // property query
                {
                    var memberAccessExpression = (MemberExpression)e;
                    Debug.Assert(memberAccessExpression.Member is PropertyInfo);

                    if (IsResult(memberAccessExpression.Member, out var awaitableFactory))
                    {
                        Split(memberAccessExpression.Expression, out r, out p);
                        p.AddResultExpression(
                            awaitable => Expression.MakeMemberAccess(awaitable, memberAccessExpression.Member),
                            awaitableFactory);
                        return;
                    }

                    r = memberAccessExpression.Expression;
                    var        parameter = Expression.Parameter(r.Type, r is ParameterExpression ope ? ope.Name : ParameterName);
                    var        property  = memberAccessExpression.GetReboundProperty();
                    MethodInfo method;
                    if (!assignment && property.CanRead(out var getter, out var getterProperty))
                    {
                        method   = getter;
                        property = getterProperty;
                    }
                    else if (property.CanWrite(out var setter, out var setterProperty))
                    {
                        method   = setter;
                        property = setterProperty;
                    }
                    else                              // This should be unreachable.
                    {
                        method = null;
                    }
                    p = new InvocationShape(
                        expression: Expression.Lambda(
                            Expression.MakeMemberAccess(parameter, property),
                            parameter),
                        method,
                        skipMatcherInitialization: assignment,
                        allowNonOverridable: allowNonOverridableLastProperty);
                    return;
                }

                default:
                    Debug.Assert(!CanSplit(e));
                    throw new InvalidOperationException();                              // this should be unreachable
                }
            }

            bool IsResult(MemberInfo member, out IAwaitableFactory awaitableFactory)
            {
                var instanceType = member.DeclaringType;

                awaitableFactory = AwaitableFactory.TryGet(instanceType);
                var returnType = member switch { PropertyInfo p => p.PropertyType,
                    _ => null };

                return(awaitableFactory != null && object.Equals(returnType, awaitableFactory.ResultType));
            }
        }