Repository navigation
Conversation
`Mock.Of<Class>` shouldn't delete invocations made from `Class`' ctor
Its only extant use was to check whether a type implements an inter- face, so turn it into a more specific method `ImplementsInterface`. We can then further simplify another `Guard` method. (As an aside, this resolves an old TODO: The `Guard` call in the ctor of `MockQueryable<T>` cannot be turned into a `Debug.Assert`, as it prevents external code (such as a rogue LINQ query operator) from con- structing an invalid `MockQueryable<T>` via `CreateQuery` when it should have `Execute`d instead.)
Make verification API more generic than `Verify()`, `VerifyAll()`
Otherwise, for recursive verification to work properly, `Setup.Try- Verify` needs to partially circumvent the `predicate` it is given, which seems illogical.
It would be nice if `Mock.Setups` reported *all* setups instead of just a subset. Given that as a goal, we will eventually need a way to recognize such setups, so let's start by exposing these two flags.
This is because up until now, the 'overridden' flags have been purely an optimization to speed up future iterations over setups but correct- ness of operation was guaranteed even without it. Since updating these flags means some computational overhead, that update does not happen in a "hot" code path, and therefore does not happen at the earliest possible time. Now that `IsOverridden` is exposed, eventual consistency is no longer good enough, as this failing test demonstrates.
(A nice side effect of that is `Verify` and `VerifyAll` becoming more self-documenting.)
Expose `Mock.Setups`, part 1: Basics
While this is a step away from being able to expose a generic `Verify(setupPredicate)` method, we need to do this for back-compat. A future `Verify(setupPredicate)` method will simply have to have slightly different behavior where the same predicate applies on both the setup & invocation sides. (This commit also removes a superfluous double predicate check.)
Allow marking conditional setups as `.Verifiable()` once again
Logic for creating completed tasks and for accessing completed tasks' results is currently strewn across the code base. It would be nice to concentrate that logic in one dedicated place.
Those will aid implementation of `IAwaitableFactory` for the standard task types (`Task`, `ValueTask`, and their generic counterparts).
Create and deconstruct awaitables using dedicated factories (`IAwaitableFactory`)
Add ability to set up the `.Result` of (value) tasks
|
Thanks for your interest in working on this! I haven't finished looking through all of your PR, however my first impression is that this exposes far too many Moq internals that shouldn't be made public. Exposing those would needlessly tie our hands in refactoring Moq in the future. The main problem in this PR is that Many other parts of your PR appear to be a direct consequence of exposing |
This is promising. Would you be willing to share your Cecil-based implementation of |
|
Thank you for your reply :) Our Cecil implementation is still very messy and needs cleaning up, but I can at least share the code of our internal class CecilProxyFactory : ProxyFactory
{
private readonly ConcurrentDictionary<CacheKey, Type> proxyTypes;
public CecilProxyFactory()
{
this.proxyTypes = new ConcurrentDictionary<CacheKey, Type>();
}
public override object CreateProxy(Type mockType, IInterceptor interceptor, Type[] interfaces, object[] arguments)
{
var type = this.GetProxyType(mockType, interfaces, arguments.Length);
var argsWithInterceptor = new object[arguments.Length + 1];
argsWithInterceptor[0] = interceptor;
arguments.CopyTo(argsWithInterceptor, 1);
var instance = Activator.CreateInstance(type, argsWithInterceptor);
if(mockType.IsDelegateType())
{
return Delegate.CreateDelegate(mockType, instance, instance.GetType().GetMethod("Invoke"));
}
return instance;
}
private Type GetProxyType(Type mockType, Type[] interfaces, int length)
{
var key = CacheKey.Create(mockType, interfaces);
return this.proxyTypes.GetOrAdd(key, key => this.CreateProxyType(mockType, interfaces));
}
private Type CreateProxyType(Type mockType, Type[] interfaces)
{
using (var builder = new AssemblyGenerator())
{
if (mockType.IsInterface)
{
var typeName = builder.DefineInterfaceProxy(mockType, interfaces);
var type = builder.BuildAssembly().GetType(typeName);
return type;
}
else if (mockType.IsDelegateType())
{
var typeName = builder.DefineDelegateContainer(mockType, interfaces);
var type = builder.BuildAssembly().GetType(typeName);
return type;
}
else
{
// Class proxy
var typeName = builder.DefineClassProxy(mockType, interfaces);
var type = builder.BuildAssembly().GetType(typeName);
return type;
}
}
throw new NotImplementedException();
}
public override bool IsMethodVisible(System.Reflection.MethodInfo method, out string messageIfNotVisible)
{
messageIfNotVisible = null;
return true;
}
public override bool IsTypeVisible(Type type)
{
return true;
}
private sealed class CacheKey
{
private readonly MemberInfo[] orderedTypes;
private readonly int hashCode;
private CacheKey(params System.Reflection.MemberInfo[] orderedTypes)
{
this.orderedTypes = orderedTypes;
this.hashCode = this.CalculateHashCode();
}
public override bool Equals(object obj)
{
var other = obj as CacheKey;
if (other == null || this.orderedTypes.Length != other.orderedTypes.Length)
{
return false;
}
for (var i = 0; i < this.orderedTypes.Length; i++)
{
if (!this.orderedTypes[i].Equals(other.orderedTypes[i]))
{
return false;
}
}
return true;
}
public override int GetHashCode()
{
return this.hashCode;
}
private int CalculateHashCode()
{
// TODO: Improve whenever
var hash = this.orderedTypes.Length;
for (var i = 0; i < this.orderedTypes.Length; i++)
{
hash = (hash * 7) ^ this.orderedTypes[i].GetHashCode();
}
return hash;
}
internal static CacheKey Create(Type t1, Type[] moreTypes)
{
var h = new HashSet<MemberInfo>() { t1 };
h.UnionWith(moreTypes);
var orderedTypes = h.OrderBy(x => x.Name, StringComparer.Ordinal).ToArray();
return new CacheKey(orderedTypes);
}
}
}As you can see, we are using the provided using Moq.Async;
using System;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
namespace Moq
{
/// <summary/>
public class CecilInvocation : Moq.IInvocation
{
/// <summary>
/// Internal type to mark invocation results as "exception occurred during execution". The type just
/// wraps the Exception so a thrown exception can be distinguished from an <see cref="System.Exception"/>
/// return value.
/// </summary>
private readonly struct ExceptionResult
{
public ExceptionResult(Exception exception)
{
Exception = exception;
}
public Exception Exception { get; }
}
private readonly object proxyInstance;
private object result;
private MethodInfo methodImplementation;
/// <summary/>
public CecilInvocation(Type proxyType
, System.Reflection.MethodInfo method
, object proxyInstance
, params object[] arguments
)
{
this.proxyInstance = proxyInstance;
this.ProxyType = proxyType;
this.Method = method;
this.Arguments = arguments;
}
/// <summary>
///
/// </summary>
public MethodInfo Method { get; }
/// <summary>
///
/// </summary>
public MethodInfo MethodImplementation
{
get
{
if (this.methodImplementation == null)
{
this.methodImplementation = this.Method.GetImplementingMethod(this.ProxyType);
}
return this.methodImplementation;
}
}
/// <summary>
///
/// </summary>
public object[] Arguments { get; }
/// <summary>
///
/// </summary>
public ISetup MatchingSetup { get; private set; }
/// <summary>
///
/// </summary>
public Type ProxyType { get; }
/// <summary>
///
/// </summary>
public bool IsVerified { get; }
/// <summary>
///
/// </summary>
public object ReturnValue
{
get => this.result is ExceptionResult ? null : this.result;
set
{
this.result = value;
}
}
/// <summary>
///
/// </summary>
public Exception Exception
{
get => this.result is ExceptionResult r ? r.Exception : null;
set
{
this.result = new ExceptionResult(value);
}
}
/// <summary/>
public object CallBase()
{
if (this.Method.IsAbstract)
{
throw new NotSupportedException();
}
// call the base method
var ftn = this.Method.MethodHandle.GetFunctionPointer();
var delegateType = this.GetDelegateType();
var d =(Delegate)Activator.CreateInstance(delegateType, this.proxyInstance, ftn);
var result = d.DynamicInvoke(this.Arguments);
return result;
}
/// <summary>
///
/// </summary>
/// <param name="awaitableFactory"></param>
public void ConvertResultToAwaitable(IAwaitableFactory awaitableFactory)
{
if (this.result is ExceptionResult r)
{
this.result = awaitableFactory.CreateFaulted(r.Exception);
}
else if (this.result != null && !this.Method.ReturnType.IsAssignableFrom(this.result.GetType()))
{
this.result = awaitableFactory.CreateCompleted(this.result);
}
}
/// <summary>
///
/// </summary>
/// <param name="setup"></param>
public void MarkAsMatchedBy(ISetup setup)
{
this.MatchingSetup = setup;
}
/// <summary>
///
/// </summary>
/// <returns></returns>
private Type GetDelegateType()
{
var types = this.Method.GetParameters()
.Select(p => p.ParameterType)
.Concat(new[] { this.Method.ReturnType });
return Expression.GetDelegateType(types.ToArray());
}
}
}Perhaps it could be possible to create another abstraction on top of |
|
|
This is a draft for the changes as per our discussion in issue #1083.
I am aware that it might need cleaning up, documentation, and so on. In this first stage, I mainly want feedback on this approach, if it's the right way to go at all.
I opted to make
ProxyFactorypublic, which I assume is desirable if we want external projects to provide their own implementations. That in turn cascaded a bit, and required me to make some more types public.In order to not leak too much implementation, I decided to expose the interface types instead of the abstract base classes. This might require some more boilerplate for alternative proxy factory implementations, but I would argue that it's worth it.
I can also confirm that with these rather crude changes, we are now able to plug in our own proxy generator, and by utilizing Cecil, we have managed to cut our test execution times nearly 2 hours down to 15 minutes.