Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

refactored Interceptor.Intercept to use a set of strategies. . #31

Merged
merged 2 commits into from

2 participants

@FelicePollano

Function was previously a little difficult to figure out due to many ifs and returns. I broken up the function in 8 different strategies, of course not yet perfect, but should be easyier to modify/read.

@kzu

Can we make the return value an enum that explains what it means to return one or the other? It's not clear to me what the boolean is in each case. Maybe InterceptionAction.Continue | Stop ?

Yep it is actually a continue / stop. I will replace with an enum. Do you think that refactoring is helpful, or did you expect something different.

Owner
@kzu
Owner
kzu commented

Much needed refactoring. It's looking very promising. Thanks for working on this.

@FelicePollano

I added the enum. Test still pass. Maybe you would also rename the strategies: My is sometimes just a guessing, maybe they are not generally understendable.

@kzu kzu merged commit f3a0141 into from
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Oct 3, 2012
  1. @FelicePollano

    refactored Interceptor.Intercept to use a set of strategies. Function…

    FelicePollano authored
    … was broken in 8 separated strategies
Commits on Oct 4, 2012
  1. @FelicePollano

    Added an enum as a result of a single interception strategy to clarif…

    FelicePollano authored
    …y when following action need to be executed
This page is out of date. Refresh to see the latest.
View
10 Source.Silverlight/Moq.Silverlight.csproj
@@ -83,7 +83,7 @@
<SpecificVersion>False</SpecificVersion>
<HintPath>..\Lib\Castle\bin-SL4\Castle.Core.dll</HintPath>
</Reference>
- <!--<Reference Include="Castle.Core, Version=1.2.0.0, Culture=neutral, PublicKeyToken=407dd0808d44fbdc, processorArchitecture=MSIL">
+ <!--<Reference Include="Castle.Core, Version=1.2.0.0, Culture=neutral, PublicKeyToken=407dd0808d44fbdc, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\Lib\Castle\sl-30\Castle.Core.dll</HintPath>
</Reference>
@@ -91,7 +91,7 @@
<SpecificVersion>False</SpecificVersion>
<HintPath>..\Lib\Castle\sl-30\Castle.DynamicProxy2.dll</HintPath>
</Reference>-->
- <Reference Include="mscorlib" />
+ <Reference Include="mscorlib" />
<Reference Include="system" />
<Reference Include="System.Core" />
</ItemGroup>
@@ -138,6 +138,9 @@
<Compile Include="..\Source\IHideObjectMembers.cs">
<Link>IHideObjectMembers.cs</Link>
</Compile>
+ <Compile Include="..\Source\IInterceptStrategy.cs">
+ <Link>IInterceptStrategy.cs</Link>
+ </Compile>
<Compile Include="..\Source\IMatcher.cs">
<Link>IMatcher.cs</Link>
</Compile>
@@ -147,6 +150,9 @@
<Compile Include="..\Source\Interceptor.cs">
<Link>Interceptor.cs</Link>
</Compile>
+ <Compile Include="..\Source\InterceptorStrategies.cs">
+ <Link>InterceptorStrategies.cs</Link>
+ </Compile>
<Compile Include="..\Source\IProxyCall.cs">
<Link>IProxyCall.cs</Link>
</Compile>
View
52 Source/IInterceptStrategy.cs
@@ -0,0 +1,52 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Moq.Proxy;
+
+namespace Moq
+{
+
+ internal enum InterceptionAction
+ {
+ Continue,Stop
+ }
+ internal interface IInterceptStrategy
+ {
+ /// <summary>
+ /// Handle interception
+ /// </summary>
+ /// <param name="invocation">the current invocation context</param>
+ /// <param name="ctx">shared data among the strategies during an interception</param>
+ /// <returns>true if further interception has to be processed, otherwise false</returns>
+ InterceptionAction HandleIntercept(ICallContext invocation, InterceptStrategyContext ctx);
+
+ }
+
+ internal class InterceptStrategyContext
+ {
+ public InterceptStrategyContext(Mock Mock
+ , Type targetType
+ , Dictionary<string, List<Delegate>> invocationLists
+ , List<ICallContext> actualInvocations
+ , MockBehavior behavior
+ , List<IProxyCall> orderedCalls
+ )
+ {
+ this.Behavior = behavior;
+ this.Mock = Mock;
+ this.InvocationLists = invocationLists;
+ this.ActualInvocations = actualInvocations;
+ this.TargetType = targetType;
+ this.OrderedCalls = orderedCalls;
+ }
+ public Mock Mock {get;private set;}
+ public Type TargetType { get; private set; }
+ public Dictionary<string, List<Delegate>> InvocationLists { get; private set; }
+ public List<ICallContext> ActualInvocations { get; private set; }
+ public MockBehavior Behavior { get; private set; }
+ public List<IProxyCall> OrderedCalls { get; private set; }
+ public IProxyCall CurrentCall { get; set; }
+ }
+
+}
View
216 Source/Interceptor.cs
@@ -130,207 +130,41 @@ public void AddCall(IProxyCall call, SetupKind kind)
orderedCalls.Add(call);
}
+ private IEnumerable<IInterceptStrategy> InterceptionStrategies()
+ {
+ yield return new HandleDestructor();
+ yield return new HandleTracking();
+ yield return new CheckMockMixing();
+ yield return new AddActualInvocation();
+ yield return new ExtractProxyCall();
+ yield return new ExecuteCall();
+ yield return new InvokeBase();
+ yield return new HandleMockRecursion();
+ }
+
[SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")]
public void Intercept(ICallContext invocation)
{
- if (invocation.Method.IsDestructor())
+
+ lock (Mock) // this solves issue #249
{
- return;
- }
-
- // Track current invocation if we're in "record" mode in a fluent invocation context.
- if (FluentMockContext.IsActive)
- {
- FluentMockContext.Current.Add(this.Mock, invocation);
- }
- lock (Mock) // this solves issue #249, but actually worsen method complexity :(
- {
- // TODO: too many ifs in this method.
- // see how to refactor with strategies.
- if (invocation.Method.DeclaringType.IsGenericType &&
- invocation.Method.DeclaringType.GetGenericTypeDefinition() == typeof(IMocked<>))
- {
- // "Mixin" of IMocked<T>.Mock
- invocation.ReturnValue = this.Mock;
- return;
- }
- else if (invocation.Method.DeclaringType == typeof(IMocked))
- {
- // "Mixin" of IMocked.Mock
- invocation.ReturnValue = this.Mock;
- return;
- }
-
- // Special case for events.
- if (!FluentMockContext.IsActive)
- {
- if (invocation.Method.IsEventAttach())
- {
- var delegateInstance = (Delegate)invocation.Arguments[0];
- // TODO: validate we can get the event?
- var eventInfo = this.GetEventFromName(invocation.Method.Name.Substring(4));
-
- if (this.Mock.CallBase)
- {
- invocation.InvokeBase();
- }
- else if (delegateInstance != null)
- {
- this.AddEventHandler(eventInfo, (Delegate)invocation.Arguments[0]);
- }
-
- return;
- }
- else if (invocation.Method.IsEventDetach())
- {
- var delegateInstance = (Delegate)invocation.Arguments[0];
- // TODO: validate we can get the event?
- var eventInfo = this.GetEventFromName(invocation.Method.Name.Substring(7));
-
- if (this.Mock.CallBase)
- {
- invocation.InvokeBase();
- }
- else if (delegateInstance != null)
- {
- this.RemoveEventHandler(eventInfo, (Delegate)invocation.Arguments[0]);
- }
-
- return;
- }
-
- // Save to support Verify[expression] pattern.
- // In a fluent invocation context, which is a recorder-like
- // mode we use to evaluate delegates by actually running them,
- // we don't want to count the invocation, or actually run
- // previous setups.
- actualInvocations.Add(invocation);
- }
-
- var call = FluentMockContext.IsActive ? (IProxyCall)null : orderedCalls.LastOrDefault(c => c.Matches(invocation));
- if (call == null && !FluentMockContext.IsActive && behavior == MockBehavior.Strict)
- {
- throw new MockException(MockException.ExceptionReason.NoSetup, behavior, invocation);
- }
-
- if (call != null)
- {
- call.SetOutParameters(invocation);
-
- // We first execute, as there may be a Throws
- // and therefore we might never get to the
- // next line.
- call.Execute(invocation);
- ThrowIfReturnValueRequired(call, invocation);
- }
- else if (invocation.Method.DeclaringType == typeof(object))
+ var interceptionContext = new InterceptStrategyContext(Mock
+ , targetType
+ , invocationLists
+ , actualInvocations
+ , behavior
+ , orderedCalls
+ );
+ foreach (var strategy in InterceptionStrategies())
{
- // Invoke underlying implementation.
- invocation.InvokeBase();
- }
- else if (invocation.Method.DeclaringType.IsClass && !invocation.Method.IsAbstract && this.Mock.CallBase)
- {
- // For mocked classes, if the target method was not abstract,
- // invoke directly.
- // Will only get here for Loose behavior.
- // TODO: we may want to provide a way to skip this by the user.
- invocation.InvokeBase();
- }
- else if (invocation.Method != null && invocation.Method.ReturnType != null &&
- invocation.Method.ReturnType != typeof(void))
- {
- Mock recursiveMock;
- if (this.Mock.InnerMocks.TryGetValue(invocation.Method, out recursiveMock))
+ if (InterceptionAction.Stop == strategy.HandleIntercept(invocation, interceptionContext))
{
- invocation.ReturnValue = recursiveMock.Object;
- }
- else
- {
- invocation.ReturnValue = this.Mock.DefaultValueProvider.ProvideDefault(invocation.Method);
+ break;
}
}
}
}
-
- /// <summary>
- /// Get an eventInfo for a given event name. Search type ancestors depth first if necessary.
- /// </summary>
- /// <param name="eventName">Name of the event, with the set_ or get_ prefix already removed</param>
- private EventInfo GetEventFromName(string eventName)
- {
- var depthFirstProgress = new Queue<Type>(this.Mock.ImplementedInterfaces.Skip(1));
- depthFirstProgress.Enqueue(targetType);
- while (depthFirstProgress.Count > 0)
- {
- var currentType = depthFirstProgress.Dequeue();
- var eventInfo = currentType.GetEvent(eventName);
- if (eventInfo != null)
- {
- return eventInfo;
- }
-
- foreach (var implementedType in GetAncestorTypes(currentType))
- {
- depthFirstProgress.Enqueue(implementedType);
- }
- }
-
- return null;
- }
-
- /// <summary>
- /// Given a type return all of its ancestors, both types and interfaces.
- /// </summary>
- /// <param name="initialType">The type to find immediate ancestors of</param>
- private static IEnumerable<Type> GetAncestorTypes(Type initialType)
- {
- var baseType = initialType.BaseType;
- if (baseType != null)
- {
- return new[] { baseType };
- }
-
- return initialType.GetInterfaces();
- }
-
- private void ThrowIfReturnValueRequired(IProxyCall call, ICallContext invocation)
- {
- if (behavior != MockBehavior.Loose &&
- invocation.Method != null &&
- invocation.Method.ReturnType != null &&
- invocation.Method.ReturnType != typeof(void))
- {
- var methodCall = call as MethodCallReturn;
- if (methodCall == null || !methodCall.HasReturnValue)
- {
- throw new MockException(
- MockException.ExceptionReason.ReturnValueRequired,
- behavior,
- invocation);
- }
- }
- }
-
- internal void AddEventHandler(EventInfo ev, Delegate handler)
- {
- List<Delegate> handlers;
- if (!this.invocationLists.TryGetValue(ev.Name, out handlers))
- {
- handlers = new List<Delegate>();
- invocationLists.Add(ev.Name, handlers);
- }
-
- handlers.Add(handler);
- }
-
- internal void RemoveEventHandler(EventInfo ev, Delegate handler)
- {
- List<Delegate> handlers;
- if (this.invocationLists.TryGetValue(ev.Name, out handlers))
- {
- handlers.Remove(handler);
- }
- }
+
internal IEnumerable<Delegate> GetInvocationList(EventInfo ev)
{
View
285 Source/InterceptorStrategies.cs
@@ -0,0 +1,285 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Moq.Proxy;
+using System.Reflection;
+
+namespace Moq
+{
+ internal class HandleMockRecursion : IInterceptStrategy
+ {
+ public InterceptionAction HandleIntercept(ICallContext invocation, InterceptStrategyContext ctx)
+ {
+ if (invocation.Method != null && invocation.Method.ReturnType != null &&
+ invocation.Method.ReturnType != typeof(void))
+ {
+ Mock recursiveMock;
+ if (ctx.Mock.InnerMocks.TryGetValue(invocation.Method, out recursiveMock))
+ {
+ invocation.ReturnValue = recursiveMock.Object;
+ }
+ else
+ {
+ invocation.ReturnValue = ctx.Mock.DefaultValueProvider.ProvideDefault(invocation.Method);
+ }
+ return InterceptionAction.Stop;
+ }
+ return InterceptionAction.Continue;
+ }
+ }
+
+ internal class InvokeBase : IInterceptStrategy
+ {
+ public InterceptionAction HandleIntercept(ICallContext invocation, InterceptStrategyContext ctx)
+ {
+ if (invocation.Method.DeclaringType == typeof(object)
+ ||
+ invocation.Method.DeclaringType.IsClass && !invocation.Method.IsAbstract && ctx.Mock.CallBase
+ )
+ {
+ // Invoke underlying implementation.
+
+ // For mocked classes, if the target method was not abstract,
+ // invoke directly.
+ // Will only get here for Loose behavior.
+ // TODO: we may want to provide a way to skip this by the user.
+ invocation.InvokeBase();
+ return InterceptionAction.Stop;
+ }
+ else
+ {
+ return InterceptionAction.Continue;
+ }
+ }
+ }
+
+ internal class ExecuteCall : IInterceptStrategy
+ {
+ InterceptStrategyContext ctx;
+ public InterceptionAction HandleIntercept(ICallContext invocation, InterceptStrategyContext ctx)
+ {
+ this.ctx = ctx;
+ if (ctx.CurrentCall != null)
+ {
+ ctx.CurrentCall.SetOutParameters(invocation);
+
+ // We first execute, as there may be a Throws
+ // and therefore we might never get to the
+ // next line.
+ ctx.CurrentCall.Execute(invocation);
+ ThrowIfReturnValueRequired(ctx.CurrentCall, invocation);
+ return InterceptionAction.Stop;
+ }
+ else
+ {
+ return InterceptionAction.Continue;
+ }
+ }
+ private void ThrowIfReturnValueRequired(IProxyCall call, ICallContext invocation)
+ {
+ if (ctx.Behavior != MockBehavior.Loose &&
+ invocation.Method != null &&
+ invocation.Method.ReturnType != null &&
+ invocation.Method.ReturnType != typeof(void))
+ {
+ var methodCall = call as MethodCallReturn;
+ if (methodCall == null || !methodCall.HasReturnValue)
+ {
+ throw new MockException(
+ MockException.ExceptionReason.ReturnValueRequired,
+ ctx.Behavior,
+ invocation);
+ }
+ }
+ }
+ }
+
+ internal class ExtractProxyCall : IInterceptStrategy
+ {
+
+ public InterceptionAction HandleIntercept(ICallContext invocation, InterceptStrategyContext ctx)
+ {
+ ctx.CurrentCall = FluentMockContext.IsActive ? (IProxyCall)null : ctx.OrderedCalls.LastOrDefault(c => c.Matches(invocation));
+ if (ctx.CurrentCall == null && !FluentMockContext.IsActive && ctx.Behavior == MockBehavior.Strict)
+ {
+ throw new MockException(MockException.ExceptionReason.NoSetup, ctx.Behavior, invocation);
+ }
+ return InterceptionAction.Continue;
+ }
+ }
+
+ internal class CheckMockMixing:IInterceptStrategy
+ {
+
+ public CheckMockMixing()
+ {
+
+ }
+ public InterceptionAction HandleIntercept(ICallContext invocation, InterceptStrategyContext ctx)
+ {
+ if (invocation.Method.DeclaringType.IsGenericType &&
+ invocation.Method.DeclaringType.GetGenericTypeDefinition() == typeof(IMocked<>))
+ {
+ // "Mixin" of IMocked<T>.Mock
+ invocation.ReturnValue = ctx.Mock;
+ return InterceptionAction.Stop;
+ }
+ else if (invocation.Method.DeclaringType == typeof(IMocked))
+ {
+ // "Mixin" of IMocked.Mock
+ invocation.ReturnValue = ctx.Mock;
+ return InterceptionAction.Stop;
+ }
+ return InterceptionAction.Continue;
+ }
+ }
+
+ internal class HandleTracking : IInterceptStrategy
+ {
+
+ public HandleTracking()
+ {
+
+ }
+ public InterceptionAction HandleIntercept(ICallContext invocation, InterceptStrategyContext ctx)
+ {
+ // Track current invocation if we're in "record" mode in a fluent invocation context.
+ if (FluentMockContext.IsActive)
+ {
+ FluentMockContext.Current.Add(ctx.Mock, invocation);
+ }
+ return InterceptionAction.Continue;
+ }
+ }
+
+ internal class HandleDestructor : IInterceptStrategy
+ {
+ public InterceptionAction HandleIntercept(ICallContext invocation, InterceptStrategyContext ctx)
+ {
+ return invocation.Method.IsDestructor()?InterceptionAction.Stop:InterceptionAction.Continue;
+ }
+ }
+
+ internal class AddActualInvocation : IInterceptStrategy
+ {
+
+ public AddActualInvocation()
+ {
+
+ }
+ /// <summary>
+ /// Get an eventInfo for a given event name. Search type ancestors depth first if necessary.
+ /// </summary>
+ /// <param name="eventName">Name of the event, with the set_ or get_ prefix already removed</param>
+ private EventInfo GetEventFromName(string eventName)
+ {
+ var depthFirstProgress = new Queue<Type>(ctx.Mock.ImplementedInterfaces.Skip(1));
+ depthFirstProgress.Enqueue(ctx.TargetType);
+ while (depthFirstProgress.Count > 0)
+ {
+ var currentType = depthFirstProgress.Dequeue();
+ var eventInfo = currentType.GetEvent(eventName);
+ if (eventInfo != null)
+ {
+ return eventInfo;
+ }
+
+ foreach (var implementedType in GetAncestorTypes(currentType))
+ {
+ depthFirstProgress.Enqueue(implementedType);
+ }
+ }
+
+ return null;
+ }
+ /// <summary>
+ /// Given a type return all of its ancestors, both types and interfaces.
+ /// </summary>
+ /// <param name="initialType">The type to find immediate ancestors of</param>
+ private static IEnumerable<Type> GetAncestorTypes(Type initialType)
+ {
+ var baseType = initialType.BaseType;
+ if (baseType != null)
+ {
+ return new[] { baseType };
+ }
+
+ return initialType.GetInterfaces();
+ }
+ internal void AddEventHandler(EventInfo ev, Delegate handler)
+ {
+ List<Delegate> handlers;
+ if (!ctx.InvocationLists.TryGetValue(ev.Name, out handlers))
+ {
+ handlers = new List<Delegate>();
+ ctx.InvocationLists.Add(ev.Name, handlers);
+ }
+
+ handlers.Add(handler);
+ }
+ internal void RemoveEventHandler(EventInfo ev, Delegate handler)
+ {
+ List<Delegate> handlers;
+ if (ctx.InvocationLists.TryGetValue(ev.Name, out handlers))
+ {
+ handlers.Remove(handler);
+ }
+ }
+ InterceptStrategyContext ctx;
+ public InterceptionAction HandleIntercept(ICallContext invocation, InterceptStrategyContext ctx)
+ {
+ this.ctx = ctx;
+ if (!FluentMockContext.IsActive)
+ {
+ //Special case for events
+ if (invocation.Method.IsEventAttach())
+ {
+ var delegateInstance = (Delegate)invocation.Arguments[0];
+ // TODO: validate we can get the event?
+ var eventInfo = this.GetEventFromName(invocation.Method.Name.Substring(4));
+
+ if (ctx.Mock.CallBase)
+ {
+ invocation.InvokeBase();
+ }
+ else if (delegateInstance != null)
+ {
+ this.AddEventHandler(eventInfo, (Delegate)invocation.Arguments[0]);
+ }
+
+ return InterceptionAction.Stop;
+ }
+ else if (invocation.Method.IsEventDetach())
+ {
+
+
+ if (ctx.Mock.CallBase)
+ {
+ invocation.InvokeBase();
+ }
+ else
+ {
+ var delegateInstance = (Delegate)invocation.Arguments[0];
+ if (delegateInstance != null)
+ {
+ // TODO: validate we can get the event?
+ var eventInfo = this.GetEventFromName(invocation.Method.Name.Substring(7));
+ this.RemoveEventHandler(eventInfo, (Delegate)invocation.Arguments[0]);
+ }
+ }
+
+ return InterceptionAction.Stop;
+ }
+
+ // Save to support Verify[expression] pattern.
+ // In a fluent invocation context, which is a recorder-like
+ // mode we use to evaluate delegates by actually running them,
+ // we don't want to count the invocation, or actually run
+ // previous setups.
+ ctx.ActualInvocations.Add(invocation);
+ }
+ return InterceptionAction.Continue;
+ }
+ }
+}
View
2  Source/Moq.csproj
@@ -71,6 +71,8 @@
</ItemGroup>
<ItemGroup>
<Compile Include="ConditionalContext.cs" />
+ <Compile Include="IInterceptStrategy.cs" />
+ <Compile Include="InterceptorStrategies.cs" />
<Compile Include="Language\ICallback.cs" />
<Compile Include="Language\ICallback.Generated.cs">
<AutoGen>True</AutoGen>
Something went wrong with that request. Please try again.