Skip to content

Commit

Permalink
Merge pull request #31 from FelicePollano/dev
Browse files Browse the repository at this point in the history
refactored Interceptor.Intercept to use a set of strategies. .
  • Loading branch information
kzu committed Feb 28, 2013
2 parents 011d58e + 0713db3 commit f3a0141
Show file tree
Hide file tree
Showing 5 changed files with 372 additions and 193 deletions.
10 changes: 8 additions & 2 deletions Source.Silverlight/Moq.Silverlight.csproj
Expand Up @@ -83,15 +83,15 @@
<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>
<Reference Include="Castle.DynamicProxy2, Version=1.2.0.0, Culture=neutral, PublicKeyToken=407dd0808d44fbdc, processorArchitecture=MSIL">
<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>
Expand Down Expand Up @@ -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>
Expand All @@ -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>
Expand Down
52 changes: 52 additions & 0 deletions 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; }
}

}
216 changes: 25 additions & 191 deletions Source/Interceptor.cs
Expand Up @@ -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)
{
Expand Down

0 comments on commit f3a0141

Please sign in to comment.