Skip to content

Commit

Permalink
Merge dev branch
Browse files Browse the repository at this point in the history
Conflicts:
	.gitignore
	Source.Silverlight/Moq.Silverlight.csproj
	Source/Interceptor.cs
	Source/It.cs
	Source/It.xdoc
	Source/MethodCall.cs
	Source/Mock.Generic.cs
	Source/Moq.csproj
	Source/Proxy/CastleProxyFactory.cs
	UnitTests/MatchersFixture.cs
	UnitTests/MockFixture.cs
	UnitTests/MockedEventsFixture.cs
	UnitTests/Moq.Tests.csproj
	UnitTests/Regressions/IssueReportsFixture.cs
	UnitTests/VerifyFixture.cs
  • Loading branch information
kzu committed Aug 23, 2013
2 parents 50b67ff + 17085ab commit 05e55d7
Show file tree
Hide file tree
Showing 18 changed files with 1,518 additions and 847 deletions.
9 changes: 7 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,18 @@ Thumbs.db
[Dd]ebug*/
*.lib
*.sbr
*.scc
*.tokens
[Bb]in
[Db]ebug*/
obj/
[Rr]elease*/
*resharper*
_ReSharper*/
*.ReSharper
*.ReSharper.user
[Tt]est[Rr]esult*
[bB]uild/
[Bb]uild[Ll]og.*
*.[Pp]ublish.xml
*.pidb
*.userprefs
*.swp
Expand Down
819 changes: 414 additions & 405 deletions Source.Silverlight/Moq.Silverlight.csproj

Large diffs are not rendered by default.

53 changes: 53 additions & 0 deletions Source/IInterceptStrategy.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
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; }
}

}
25 changes: 25 additions & 0 deletions Source/IMock.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using System.Diagnostics.CodeAnalysis;

namespace Moq
{
/// <summary>
/// Covarient interface for Mock&lt;T&gt; such that casts between IMock&lt;Employee&gt; to IMock&lt;Person&gt;
/// are possible. Only covers the covariant members of Mock&lt;T&gt;.
/// </summary>
public interface IMock<out T> where T : class
{
/// <include file='Mock.Generic.xdoc' path='docs/doc[@for="Mock{T}.Object"]/*'/>
[SuppressMessage("Microsoft.Naming", "CA1716:IdentifiersShouldNotMatchKeywords", MessageId = "Object", Justification = "Exposes the mocked object instance, so it's appropriate.")]
[SuppressMessage("Microsoft.Naming", "CA1721:PropertyNamesShouldNotMatchGetMethods", Justification = "The public Object property is the only one visible to Moq consumers. The protected member is for internal use only.")]
T Object { get; }

/// <include file='Mock.xdoc' path='docs/doc[@for="Mock.Behavior"]/*'/>
MockBehavior Behavior { get; }

/// <include file='Mock.xdoc' path='docs/doc[@for="Mock.CallBase"]/*'/>
bool CallBase { get; set; }

/// <include file='Mock.xdoc' path='docs/doc[@for="Mock.DefaultValue"]/*'/>
DefaultValue DefaultValue { get; set; }
}
}
232 changes: 37 additions & 195 deletions Source/Interceptor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,13 @@ public Interceptor(MockBehavior behavior, Type targetType, Mock mock)

internal IEnumerable<ICallContext> ActualCalls
{
get { return this.actualInvocations; }
get
{
lock (actualInvocations)
{
return this.actualInvocations.ToList();
}
}
}

internal Mock Mock { get; private set; }
Expand Down Expand Up @@ -130,205 +136,41 @@ public void AddCall(IProxyCall call, SetupKind kind)
orderedCalls.Add(call);
}

[SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")]
public void Intercept(ICallContext invocation)
private IEnumerable<IInterceptStrategy> InterceptionStrategies()
{
if (invocation.Method.IsDestructor())
{
return;
}

// Track current invocation if we're in "record" mode in a fluent invocation context.
if (FluentMockContext.IsActive)
{
FluentMockContext.Current.Add(this.Mock, invocation);
}

// 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 && !eventInfo.DeclaringType.IsInterface)
{
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 && !eventInfo.DeclaringType.IsInterface)
{
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))
{
// 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))
{
invocation.ReturnValue = recursiveMock.Object;
}
else
{
invocation.ReturnValue = this.Mock.DefaultValueProvider.ProvideDefault(invocation.Method);
}
}
}

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

/// <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)
[SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")]
public void Intercept(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);
}
lock (Mock) // this solves issue #249
{
var interceptionContext = new InterceptStrategyContext(Mock
, targetType
, invocationLists
, actualInvocations
, behavior
, orderedCalls
);
foreach (var strategy in InterceptionStrategies())
{
if (InterceptionAction.Stop == strategy.HandleIntercept(invocation, interceptionContext))
{
break;
}
}
}
}


internal IEnumerable<Delegate> GetInvocationList(EventInfo ev)
{
Expand Down Expand Up @@ -414,4 +256,4 @@ protected override Expression VisitConstant(ConstantExpression c)
}
}
}
}
}
Loading

0 comments on commit 05e55d7

Please sign in to comment.