/// <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>(); }
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); }
/// <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(); }
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); } }
/// <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)); } }