예제 #1
0
파일: Mock.cs 프로젝트: lxf/moq4
        private static TCall SetupSetImpl <T, TCall>(
            Mock <T> mock,
            Action <T> setterExpression,
            Func <Mock, Expression, MethodInfo, Expression[], TCall> callFactory)
            where T : class
            where TCall : MethodCall
        {
            using (var context = new FluentMockContext())
            {
                setterExpression(mock.Object);

                var last = context.LastInvocation;
                if (last == null)
                {
                    throw new ArgumentException(string.Format(
                                                    CultureInfo.InvariantCulture,
                                                    Resources.SetupOnNonOverridableMember,
                                                    string.Empty));
                }

                var setter = last.Invocation.Method;
                if (!setter.IsPropertySetter())
                {
                    throw new ArgumentException(Resources.SetupNotSetter);
                }

                // No need to call ThrowIfCantOverride as non-overridable would have thrown above already.

                // Get the variable name as used in the actual delegate :)
                // because of delegate currying, look at the last parameter for the Action's backing method, not the first
                var setterExpressionParameters = setterExpression.Method.GetParameters();
                var parameterName = setterExpressionParameters[setterExpressionParameters.Length - 1].Name;
                var x             = Expression.Parameter(last.Invocation.Method.DeclaringType, parameterName);

                var arguments  = last.Invocation.Arguments;
                var parameters = setter.GetParameters();
                var values     = new Expression[arguments.Length];

                if (last.Match == null)
                {
                    // Length == 1 || Length == 2 (Indexer property)
                    for (int i = 0; i < arguments.Length; i++)
                    {
                        values[i] = GetValueExpression(arguments[i], parameters[i].ParameterType);
                    }

                    var lambda = Expression.Lambda(
                        typeof(Action <>).MakeGenericType(x.Type),
                        Expression.Call(x, last.Invocation.Method, values),
                        x);

                    return(callFactory(last.Mock, lambda, last.Invocation.Method, values));
                }
                else
                {
                    var matchers     = new Expression[arguments.Length];
                    var valueIndex   = arguments.Length - 1;
                    var propertyType = setter.GetParameters()[valueIndex].ParameterType;

                    // If the value matcher is not equal to the property
                    // type (i.e. prop is int?, but you use It.IsAny<int>())
                    // add a cast.
                    if (last.Match.RenderExpression.Type != propertyType)
                    {
                        values[valueIndex] = Expression.Convert(last.Match.RenderExpression, propertyType);
                    }
                    else
                    {
                        values[valueIndex] = last.Match.RenderExpression;
                    }

                    matchers[valueIndex] = new MatchExpression(last.Match);

                    if (arguments.Length == 2)
                    {
                        // TODO: what about multi-index setters?
                        // Add the index value for the property indexer
                        values[0] = GetValueExpression(arguments[0], parameters[0].ParameterType);
                        // TODO: No matcher supported now for the index
                        matchers[0] = values[0];
                    }

                    var lambda = Expression.Lambda(
                        typeof(Action <>).MakeGenericType(x.Type),
                        Expression.Call(x, last.Invocation.Method, values),
                        x);

                    return(callFactory(last.Mock, lambda, last.Invocation.Method, matchers));
                }
            }
        }
예제 #2
0
 private void ToStringMatch(MatchExpression node)
 {
     ToString(node.Match.RenderExpression);
     return;
 }
예제 #3
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)
                    {
                        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);
            }
        }
예제 #4
0
 private static StringBuilder AppendExpression(this StringBuilder builder, MatchExpression expression)
 {
     return(builder.AppendExpression(expression.Match.RenderExpression));
 }
예제 #5
0
        private static SetupSetImplResult SetupSetImpl(Mock mock, Delegate setterExpression)
        {
            Mock       target;
            Invocation invocation;

            AmbientObserver.Matches matches;

            using (var observer = AmbientObserver.Activate())
            {
                setterExpression.DynamicInvoke(mock.Object);

                if (!observer.LastIsInvocation(out target, out invocation, out matches))
                {
                    throw new ArgumentException(string.Format(
                                                    CultureInfo.InvariantCulture,
                                                    Resources.SetupOnNonVirtualMember,
                                                    string.Empty));
                }
            }

            var setter = invocation.Method;

            if (!setter.IsPropertySetter())
            {
                throw new ArgumentException(Resources.SetupNotSetter);
            }

            // No need to call ThrowIfCantOverride as non-overridable would have thrown above already.

            // Get the variable name as used in the actual delegate :)
            // because of delegate currying, look at the last parameter for the Action's backing method, not the first
            var setterExpressionParameters = setterExpression.GetMethodInfo().GetParameters();
            var parameterName = setterExpressionParameters[setterExpressionParameters.Length - 1].Name;
            var x             = Expression.Parameter(invocation.Method.DeclaringType, parameterName);

            var arguments  = invocation.Arguments;
            var parameters = setter.GetParameters();
            var values     = new Expression[arguments.Length];

            if (matches.Count == 0)
            {
                // Length == 1 || Length == 2 (Indexer property)
                for (int i = 0; i < arguments.Length; i++)
                {
                    values[i] = GetValueExpression(arguments[i], parameters[i].ParameterType);
                }

                var lambda = Expression.Lambda(
                    typeof(Action <>).MakeGenericType(x.Type),
                    Expression.Call(x, invocation.Method, values),
                    x);

                return(new SetupSetImplResult(target, lambda, invocation.Method, values));
            }
            else
            {
                // TODO: Use all observed matchers, not just the last one!
                var lastMatch = matches[matches.Count - 1];

                var matchers     = new Expression[arguments.Length];
                var valueIndex   = arguments.Length - 1;
                var propertyType = setter.GetParameters()[valueIndex].ParameterType;

                // If the value matcher is not equal to the property
                // type (i.e. prop is int?, but you use It.IsAny<int>())
                // add a cast.
                if (lastMatch.RenderExpression.Type != propertyType)
                {
                    values[valueIndex] = Expression.Convert(lastMatch.RenderExpression, propertyType);
                }
                else
                {
                    values[valueIndex] = lastMatch.RenderExpression;
                }

                matchers[valueIndex] = new MatchExpression(lastMatch);

                for (int i = 0; i < arguments.Length - 1; i++)
                {
                    // Add the index value for the property indexer
                    values[i] = GetValueExpression(arguments[i], parameters[i].ParameterType);
                    // TODO: No matcher supported now for the index
                    matchers[i] = values[i];
                }

                var lambda = Expression.Lambda(
                    typeof(Action <>).MakeGenericType(x.Type),
                    Expression.Call(x, invocation.Method, values),
                    x);

                return(new SetupSetImplResult(target, lambda, invocation.Method, matchers));
            }
        }