From 31c854ff887f503c2b775325834597aaf9261d82 Mon Sep 17 00:00:00 2001
From: Robert Coltheart <13191652+robertcoltheart@users.noreply.github.com>
Date: Mon, 16 Dec 2024 08:25:05 +1100
Subject: [PATCH] Move fakes to main repo (#536)
* Move fakes to main repo
* target frameworks
---
Machine.Specifications.sln | 12 +
.../DeepFake.cs | 273 ++++++++++++++++++
.../Machine.Specifications.Fakes.Specs.csproj | 20 ++
.../TestSpec.cs | 59 ++++
.../Machine.Specifications.Fakes.csproj | 12 +
.../Proxy/IInterceptor.cs | 7 +
.../Proxy/IInvocation.cs | 13 +
.../Proxy/IProxyFactory.cs | 9 +
.../Proxy/Invocation.cs | 19 ++
.../Proxy/ProxyFactory.cs | 56 ++++
.../Proxy/ProxyInvocation.cs | 19 ++
.../Proxy/Reflection/ClassProxyVisitor.cs | 12 +
.../Proxy/Reflection/DelegateProxyVisitor.cs | 7 +
.../Proxy/Reflection/ILGeneratorExtensions.cs | 35 +++
.../Proxy/Reflection/IProxyDefinition.cs | 11 +
.../Proxy/Reflection/ITypeEmitter.cs | 16 +
.../Proxy/Reflection/ITypeEmitterFactory.cs | 9 +
.../Proxy/Reflection/InterfaceProxyVisitor.cs | 16 +
.../Proxy/Reflection/MemberInfoExtensions.cs | 34 +++
.../Proxy/Reflection/ProxyDefinition.cs | 19 ++
.../Proxy/Reflection/ProxyVisitor.cs | 33 +++
.../Proxy/Reflection/TypeEmitter.cs | 187 ++++++++++++
.../Proxy/Reflection/TypeEmitterFactory.cs | 36 +++
23 files changed, 914 insertions(+)
create mode 100644 src/Machine.Specifications.Fakes.Specs/DeepFake.cs
create mode 100644 src/Machine.Specifications.Fakes.Specs/Machine.Specifications.Fakes.Specs.csproj
create mode 100644 src/Machine.Specifications.Fakes.Specs/TestSpec.cs
create mode 100644 src/Machine.Specifications.Fakes/Machine.Specifications.Fakes.csproj
create mode 100644 src/Machine.Specifications.Fakes/Proxy/IInterceptor.cs
create mode 100644 src/Machine.Specifications.Fakes/Proxy/IInvocation.cs
create mode 100644 src/Machine.Specifications.Fakes/Proxy/IProxyFactory.cs
create mode 100644 src/Machine.Specifications.Fakes/Proxy/Invocation.cs
create mode 100644 src/Machine.Specifications.Fakes/Proxy/ProxyFactory.cs
create mode 100644 src/Machine.Specifications.Fakes/Proxy/ProxyInvocation.cs
create mode 100644 src/Machine.Specifications.Fakes/Proxy/Reflection/ClassProxyVisitor.cs
create mode 100644 src/Machine.Specifications.Fakes/Proxy/Reflection/DelegateProxyVisitor.cs
create mode 100644 src/Machine.Specifications.Fakes/Proxy/Reflection/ILGeneratorExtensions.cs
create mode 100644 src/Machine.Specifications.Fakes/Proxy/Reflection/IProxyDefinition.cs
create mode 100644 src/Machine.Specifications.Fakes/Proxy/Reflection/ITypeEmitter.cs
create mode 100644 src/Machine.Specifications.Fakes/Proxy/Reflection/ITypeEmitterFactory.cs
create mode 100644 src/Machine.Specifications.Fakes/Proxy/Reflection/InterfaceProxyVisitor.cs
create mode 100644 src/Machine.Specifications.Fakes/Proxy/Reflection/MemberInfoExtensions.cs
create mode 100644 src/Machine.Specifications.Fakes/Proxy/Reflection/ProxyDefinition.cs
create mode 100644 src/Machine.Specifications.Fakes/Proxy/Reflection/ProxyVisitor.cs
create mode 100644 src/Machine.Specifications.Fakes/Proxy/Reflection/TypeEmitter.cs
create mode 100644 src/Machine.Specifications.Fakes/Proxy/Reflection/TypeEmitterFactory.cs
diff --git a/Machine.Specifications.sln b/Machine.Specifications.sln
index afd25678..a252273e 100644
--- a/Machine.Specifications.sln
+++ b/Machine.Specifications.sln
@@ -25,6 +25,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Machine.Specifications.Anal
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Machine.Specifications.Analyzers.Tests", "src\Machine.Specifications.Analyzers.Tests\Machine.Specifications.Analyzers.Tests.csproj", "{402EF260-6140-4633-880B-18BEBF7B9DA2}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Machine.Specifications.Fakes", "src\Machine.Specifications.Fakes\Machine.Specifications.Fakes.csproj", "{2DBF8D62-8A2F-4BE1-B5F3-82910AD277F7}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Machine.Specifications.Fakes.Specs", "src\Machine.Specifications.Fakes.Specs\Machine.Specifications.Fakes.Specs.csproj", "{B897126F-BF01-4A97-965D-C2DE1BC63460}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -75,6 +79,14 @@ Global
{402EF260-6140-4633-880B-18BEBF7B9DA2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{402EF260-6140-4633-880B-18BEBF7B9DA2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{402EF260-6140-4633-880B-18BEBF7B9DA2}.Release|Any CPU.Build.0 = Release|Any CPU
+ {2DBF8D62-8A2F-4BE1-B5F3-82910AD277F7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {2DBF8D62-8A2F-4BE1-B5F3-82910AD277F7}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {2DBF8D62-8A2F-4BE1-B5F3-82910AD277F7}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {2DBF8D62-8A2F-4BE1-B5F3-82910AD277F7}.Release|Any CPU.Build.0 = Release|Any CPU
+ {B897126F-BF01-4A97-965D-C2DE1BC63460}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B897126F-BF01-4A97-965D-C2DE1BC63460}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B897126F-BF01-4A97-965D-C2DE1BC63460}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B897126F-BF01-4A97-965D-C2DE1BC63460}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/src/Machine.Specifications.Fakes.Specs/DeepFake.cs b/src/Machine.Specifications.Fakes.Specs/DeepFake.cs
new file mode 100644
index 00000000..0ba30e7e
--- /dev/null
+++ b/src/Machine.Specifications.Fakes.Specs/DeepFake.cs
@@ -0,0 +1,273 @@
+using System;
+using System.Reflection;
+using Machine.Specifications.Fakes.Proxy;
+
+namespace Machine.Specifications.Fakes.Specs
+{
+ public interface IMyInterface
+ {
+ bool ReadProp { get; }
+
+ bool WriteProp { set; }
+
+ bool ReadWriteProp { get; set; }
+
+ event EventHandler Event;
+
+ bool Method(int arg1, ref int arg2, out int arg3);
+ }
+
+ public class MyClass
+ {
+ public MyClass(int arg1, int arg2)
+ {
+ }
+
+ public virtual bool ReadProp { get; }
+
+ public virtual bool WriteProp
+ {
+ set
+ {
+ }
+ }
+
+ public virtual bool ReadWriteProp { get; set; }
+
+ public virtual event EventHandler Event;
+
+ public virtual bool Method(int arg1, ref int arg2, out int arg3)
+ {
+ arg3 = default;
+
+ return default;
+ }
+ }
+
+ public delegate bool MyDelegate(int arg1, ref int arg2, out int arg3);
+
+ public class MyInterfaceFake : IMyInterface
+ {
+ private static readonly MethodInfo MethodHandle = typeof(IMyInterface).GetMethod("Method");
+ private static readonly MethodInfo ReadPropGetHandle = typeof(IMyInterface).GetMethod("get_ReadProp");
+ private static readonly MethodInfo WritePropSetHandle = typeof(IMyInterface).GetMethod("set_WriteProp");
+ private static readonly MethodInfo ReadWritePropGetHandle = typeof(IMyInterface).GetMethod("get_ReadWriteProp");
+ private static readonly MethodInfo ReadWritePropSetHandle = typeof(IMyInterface).GetMethod("set_ReadWriteProp");
+ private static readonly MethodInfo EventAddHandle = typeof(IMyInterface).GetMethod("add_Event");
+ private static readonly MethodInfo EventRemoveHandle = typeof(IMyInterface).GetMethod("remove_Event");
+
+ private readonly IInterceptor interceptor;
+
+ public MyInterfaceFake(IInterceptor interceptor)
+ {
+ this.interceptor = interceptor;
+ }
+
+ public bool ReadProp
+ {
+ get
+ {
+ var arguments = new object[0];
+ var invocation = new Invocation(ReadPropGetHandle, arguments);
+
+ interceptor.Intercept(invocation);
+
+ return (bool) invocation.ReturnValue;
+ }
+ }
+
+ public bool WriteProp
+ {
+ set
+ {
+ var arguments = new object[] {value};
+ var invocation = new Invocation(WritePropSetHandle, arguments);
+
+ interceptor.Intercept(invocation);
+ }
+ }
+
+ public bool ReadWriteProp
+ {
+ get
+ {
+ var arguments = new object[0];
+ var invocation = new Invocation(ReadWritePropGetHandle, arguments);
+
+ interceptor.Intercept(invocation);
+
+ return (bool) invocation.ReturnValue;
+ }
+ set
+ {
+ var arguments = new object[] {value};
+ var invocation = new Invocation(ReadWritePropSetHandle, arguments);
+
+ interceptor.Intercept(invocation);
+ }
+ }
+
+ public event EventHandler Event
+ {
+ add
+ {
+ var arguments = new object[] {value};
+ var invocation = new Invocation(EventAddHandle, arguments);
+
+ interceptor.Intercept(invocation);
+ }
+ remove
+ {
+ var arguments = new object[] {value};
+ var invocation = new Invocation(EventRemoveHandle, arguments);
+
+ interceptor.Intercept(invocation);
+ }
+ }
+
+ public bool Method(int arg1, ref int arg2, out int arg3)
+ {
+ arg3 = default;
+
+ var arguments = new object[] {arg1, arg2, arg3};
+ var invocation = new Invocation(MethodHandle, arguments);
+
+ interceptor.Intercept(invocation);
+
+ arg2 = (int) arguments[1];
+ arg3 = (int) arguments[2];
+
+ return (bool) invocation.ReturnValue;
+ }
+ }
+
+ public class MyClassFake : MyClass
+ {
+ private static readonly MethodInfo MethodHandle = typeof(MyClass).GetMethod("Method");
+ private static readonly MethodInfo ReadPropGetHandle = typeof(MyClass).GetMethod("get_ReadProp");
+ private static readonly MethodInfo WritePropSetHandle = typeof(MyClass).GetMethod("set_WriteProp");
+ private static readonly MethodInfo ReadWritePropGetHandle = typeof(MyClass).GetMethod("get_ReadWriteProp");
+ private static readonly MethodInfo ReadWritePropSetHandle = typeof(MyClass).GetMethod("set_ReadWriteProp");
+ private static readonly MethodInfo EventAddHandle = typeof(MyClass).GetMethod("add_Event");
+ private static readonly MethodInfo EventRemoveHandle = typeof(MyClass).GetMethod("remove_Event");
+
+ private readonly IInterceptor interceptor;
+
+ public MyClassFake(IInterceptor interceptor, int arg1, int arg2)
+ : base(arg1, arg2)
+ {
+ this.interceptor = interceptor;
+ }
+
+ public override bool ReadProp
+ {
+ get
+ {
+ var arguments = new object[0];
+ var invocation = new Invocation(ReadPropGetHandle, arguments);
+
+ interceptor.Intercept(invocation);
+
+ return (bool) invocation.ReturnValue;
+ }
+ }
+
+ public override bool WriteProp
+ {
+ set
+ {
+ var arguments = new object[] {value};
+ var invocation = new Invocation(WritePropSetHandle, arguments);
+
+ interceptor.Intercept(invocation);
+ }
+ }
+
+ public override bool ReadWriteProp
+ {
+ get
+ {
+ var arguments = new object[0];
+ var invocation = new Invocation(ReadWritePropGetHandle, arguments);
+
+ interceptor.Intercept(invocation);
+
+ return (bool) invocation.ReturnValue;
+ }
+ set
+ {
+ var arguments = new object[] {value};
+ var invocation = new Invocation(ReadWritePropSetHandle, arguments);
+
+ interceptor.Intercept(invocation);
+ }
+ }
+
+ public override event EventHandler Event
+ {
+ add
+ {
+ var arguments = new object[] {value};
+ var invocation = new Invocation(EventAddHandle, arguments);
+
+ interceptor.Intercept(invocation);
+ }
+ remove
+ {
+ var arguments = new object[] {value};
+ var invocation = new Invocation(EventRemoveHandle, arguments);
+
+ interceptor.Intercept(invocation);
+ }
+ }
+
+ public override bool Method(int arg1, ref int arg2, out int arg3)
+ {
+ arg3 = default;
+
+ var arguments = new object[] {arg1, arg2, arg3};
+ var invocation = new Invocation(MethodHandle, arguments);
+
+ interceptor.Intercept(invocation);
+
+ arg2 = (int) arguments[1];
+ arg3 = (int) arguments[2];
+
+ return (bool) invocation.ReturnValue;
+ }
+ }
+
+ public class MyDelegateFake
+ {
+ private static readonly MethodInfo MethodHandle = typeof(MyDelegate).GetMethod("Invoke");
+
+ private readonly IInterceptor interceptor;
+
+ public MyDelegateFake(IInterceptor interceptor)
+ {
+ this.interceptor = interceptor;
+ }
+
+ public bool Invoke(int arg1, ref int arg2, out int arg3)
+ {
+ arg3 = default;
+
+ var arguments = new object[] {arg1, arg2, arg3};
+ var invocation = new Invocation(MethodHandle, arguments);
+
+ interceptor.Intercept(invocation);
+
+ arg2 = (int) arguments[1];
+ arg3 = (int) arguments[2];
+
+ return (bool) invocation.ReturnValue;
+ }
+
+ public static MyDelegate Create(IInterceptor interceptor)
+ {
+ var target = new MyDelegateFake(interceptor);
+
+ return (MyDelegate) Delegate.CreateDelegate(typeof(MyDelegate), target, "Invoke");
+ }
+ }
+}
diff --git a/src/Machine.Specifications.Fakes.Specs/Machine.Specifications.Fakes.Specs.csproj b/src/Machine.Specifications.Fakes.Specs/Machine.Specifications.Fakes.Specs.csproj
new file mode 100644
index 00000000..ee9fcb60
--- /dev/null
+++ b/src/Machine.Specifications.Fakes.Specs/Machine.Specifications.Fakes.Specs.csproj
@@ -0,0 +1,20 @@
+
+
+
+ net8.0
+
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Machine.Specifications.Fakes.Specs/TestSpec.cs b/src/Machine.Specifications.Fakes.Specs/TestSpec.cs
new file mode 100644
index 00000000..2f8b7b3f
--- /dev/null
+++ b/src/Machine.Specifications.Fakes.Specs/TestSpec.cs
@@ -0,0 +1,59 @@
+using System;
+using System.Linq;
+using System.Reflection;
+using Machine.Specifications.Fakes.Proxy;
+using Machine.Specifications.Fakes.Proxy.Reflection;
+
+namespace Machine.Specifications.Fakes.Specs
+{
+ public interface IRob
+ {
+ object Execute(int arg1);
+ }
+
+ public class Interceptor : IInterceptor
+ {
+ public void Intercept(IInvocation invocation)
+ {
+ invocation.ReturnValue = null;
+ }
+ }
+
+ class TestSpec
+ {
+ static TypeEmitterFactory factory;
+
+ static MethodInfo execute_method;
+
+ static Type type;
+
+ static Interceptor interceptor;
+
+ Establish context = () =>
+ {
+ factory = new TypeEmitterFactory();
+ interceptor = new Interceptor();
+ execute_method = typeof(IRob).GetMethod("Execute", BindingFlags.Instance | BindingFlags.Public);
+ };
+
+ Because of = () =>
+ {
+ var emitter = factory.DefineType(typeof(object));
+
+ emitter.EmitInterface(typeof(IRob));
+ emitter.EmitConstructor(typeof(object).GetConstructors().First());
+ emitter.EmitMethod(execute_method);
+
+ type = emitter.CreateType();
+ };
+
+ It should = () =>
+ {
+ var value = Activator.CreateInstance(type, interceptor);
+
+ var ret = execute_method.Invoke(value, new object[] {4});
+
+ int r = 1;
+ };
+ }
+}
diff --git a/src/Machine.Specifications.Fakes/Machine.Specifications.Fakes.csproj b/src/Machine.Specifications.Fakes/Machine.Specifications.Fakes.csproj
new file mode 100644
index 00000000..b26d9a24
--- /dev/null
+++ b/src/Machine.Specifications.Fakes/Machine.Specifications.Fakes.csproj
@@ -0,0 +1,12 @@
+
+
+
+ net472;netstandard2.0
+ false
+
+
+
+
+
+
+
diff --git a/src/Machine.Specifications.Fakes/Proxy/IInterceptor.cs b/src/Machine.Specifications.Fakes/Proxy/IInterceptor.cs
new file mode 100644
index 00000000..246d45fe
--- /dev/null
+++ b/src/Machine.Specifications.Fakes/Proxy/IInterceptor.cs
@@ -0,0 +1,7 @@
+namespace Machine.Specifications.Fakes.Proxy
+{
+ public interface IInterceptor
+ {
+ void Intercept(IInvocation invocation);
+ }
+}
diff --git a/src/Machine.Specifications.Fakes/Proxy/IInvocation.cs b/src/Machine.Specifications.Fakes/Proxy/IInvocation.cs
new file mode 100644
index 00000000..50e1410a
--- /dev/null
+++ b/src/Machine.Specifications.Fakes/Proxy/IInvocation.cs
@@ -0,0 +1,13 @@
+using System.Reflection;
+
+namespace Machine.Specifications.Fakes.Proxy
+{
+ public interface IInvocation
+ {
+ MethodInfo Method { get; }
+
+ object[] Arguments { get; }
+
+ object ReturnValue { get; set; }
+ }
+}
diff --git a/src/Machine.Specifications.Fakes/Proxy/IProxyFactory.cs b/src/Machine.Specifications.Fakes/Proxy/IProxyFactory.cs
new file mode 100644
index 00000000..7dd08ef0
--- /dev/null
+++ b/src/Machine.Specifications.Fakes/Proxy/IProxyFactory.cs
@@ -0,0 +1,9 @@
+using System;
+
+namespace Machine.Specifications.Fakes.Proxy
+{
+ public interface IProxyFactory
+ {
+ object CreateProxy(Type type, IInterceptor interceptor, params object[] arguments);
+ }
+}
diff --git a/src/Machine.Specifications.Fakes/Proxy/Invocation.cs b/src/Machine.Specifications.Fakes/Proxy/Invocation.cs
new file mode 100644
index 00000000..37217ab5
--- /dev/null
+++ b/src/Machine.Specifications.Fakes/Proxy/Invocation.cs
@@ -0,0 +1,19 @@
+using System.Reflection;
+
+namespace Machine.Specifications.Fakes.Proxy
+{
+ public class Invocation : IInvocation
+ {
+ public Invocation(MethodInfo method, object[] arguments)
+ {
+ Method = method;
+ Arguments = arguments;
+ }
+
+ public MethodInfo Method { get; }
+
+ public object[] Arguments { get; }
+
+ public object ReturnValue { get; set; }
+ }
+}
diff --git a/src/Machine.Specifications.Fakes/Proxy/ProxyFactory.cs b/src/Machine.Specifications.Fakes/Proxy/ProxyFactory.cs
new file mode 100644
index 00000000..a434af7c
--- /dev/null
+++ b/src/Machine.Specifications.Fakes/Proxy/ProxyFactory.cs
@@ -0,0 +1,56 @@
+using System;
+using System.Collections.Concurrent;
+using Machine.Specifications.Fakes.Proxy.Reflection;
+
+namespace Machine.Specifications.Fakes.Proxy
+{
+ public class ProxyFactory : IProxyFactory
+ {
+ private readonly ITypeEmitterFactory typeEmitterFactory;
+
+ private readonly ConcurrentDictionary> proxies =
+ new ConcurrentDictionary>();
+
+ public ProxyFactory(ITypeEmitterFactory typeEmitterFactory)
+ {
+ this.typeEmitterFactory = typeEmitterFactory;
+ }
+
+ public object CreateProxy(Type type, IInterceptor interceptor, params object[] arguments)
+ {
+ var proxy = proxies.GetOrAdd(type, CreateProxy);
+
+ return proxy.Value.Create(interceptor, arguments);
+ }
+
+ private Lazy CreateProxy(Type type)
+ {
+ return new Lazy(() => GetProxyDefinition(type));
+ }
+
+ private IProxyDefinition GetProxyDefinition(Type type)
+ {
+ var emitter = typeEmitterFactory.DefineType(type);
+
+ var visitor = GetProxyVisitor(type, emitter);
+ visitor.Visit(type);
+
+ return new ProxyDefinition(emitter.CreateType());
+ }
+
+ private ProxyVisitor GetProxyVisitor(Type type, ITypeEmitter emitter)
+ {
+ if (type.IsInterface)
+ {
+ return new InterfaceProxyVisitor(type);
+ }
+
+ if (typeof(Delegate).IsAssignableFrom(type))
+ {
+ return new DelegateProxyVisitor();
+ }
+
+ return new ClassProxyVisitor(emitter);
+ }
+ }
+}
diff --git a/src/Machine.Specifications.Fakes/Proxy/ProxyInvocation.cs b/src/Machine.Specifications.Fakes/Proxy/ProxyInvocation.cs
new file mode 100644
index 00000000..9092b795
--- /dev/null
+++ b/src/Machine.Specifications.Fakes/Proxy/ProxyInvocation.cs
@@ -0,0 +1,19 @@
+using System.Reflection;
+
+namespace Machine.Specifications.Fakes.Proxy
+{
+ public abstract class ProxyInvocation : IInvocation
+ {
+ protected ProxyInvocation(MethodInfo method, object[] arguments)
+ {
+ Method = method;
+ Arguments = arguments;
+ }
+
+ public MethodInfo Method { get; }
+
+ public object[] Arguments { get; }
+
+ public object ReturnValue { get; set; }
+ }
+}
diff --git a/src/Machine.Specifications.Fakes/Proxy/Reflection/ClassProxyVisitor.cs b/src/Machine.Specifications.Fakes/Proxy/Reflection/ClassProxyVisitor.cs
new file mode 100644
index 00000000..62368934
--- /dev/null
+++ b/src/Machine.Specifications.Fakes/Proxy/Reflection/ClassProxyVisitor.cs
@@ -0,0 +1,12 @@
+namespace Machine.Specifications.Fakes.Proxy.Reflection
+{
+ public class ClassProxyVisitor : ProxyVisitor
+ {
+ private ITypeEmitter typeEmitter;
+
+ public ClassProxyVisitor(ITypeEmitter typeEmitter)
+ {
+ this.typeEmitter = typeEmitter;
+ }
+ }
+}
diff --git a/src/Machine.Specifications.Fakes/Proxy/Reflection/DelegateProxyVisitor.cs b/src/Machine.Specifications.Fakes/Proxy/Reflection/DelegateProxyVisitor.cs
new file mode 100644
index 00000000..56943d25
--- /dev/null
+++ b/src/Machine.Specifications.Fakes/Proxy/Reflection/DelegateProxyVisitor.cs
@@ -0,0 +1,7 @@
+namespace Machine.Specifications.Fakes.Proxy.Reflection
+{
+ public class DelegateProxyVisitor : ProxyVisitor
+ {
+
+ }
+}
diff --git a/src/Machine.Specifications.Fakes/Proxy/Reflection/ILGeneratorExtensions.cs b/src/Machine.Specifications.Fakes/Proxy/Reflection/ILGeneratorExtensions.cs
new file mode 100644
index 00000000..a92359bb
--- /dev/null
+++ b/src/Machine.Specifications.Fakes/Proxy/Reflection/ILGeneratorExtensions.cs
@@ -0,0 +1,35 @@
+using System;
+using System.Reflection.Emit;
+
+namespace Machine.Specifications.Fakes.Proxy.Reflection
+{
+ public static class ILGeneratorExtensions
+ {
+ public static void EmitBox(this ILGenerator generator, Type type)
+ {
+ if (type.IsValueType || type.IsGenericParameter)
+ generator.Emit(OpCodes.Box, type);
+ }
+
+ public static void EmitLoadArgument(this ILGenerator generator, LocalBuilder variable)
+ {
+ generator.Emit(OpCodes.Ldarg, variable);
+ }
+
+ public static void EmitLoadValue(this ILGenerator generator, int value)
+ {
+ generator.Emit(OpCodes.Ldc_I4, value);
+ }
+
+ public static LocalBuilder EmitNewArray(this ILGenerator generator, Type elementType, int size)
+ {
+ var variable = generator.DeclareLocal(elementType.MakeArrayType());
+
+ generator.EmitLoadValue(size);
+ generator.Emit(OpCodes.Newarr, elementType);
+ generator.Emit(OpCodes.Stloc, variable);
+
+ return variable;
+ }
+ }
+}
diff --git a/src/Machine.Specifications.Fakes/Proxy/Reflection/IProxyDefinition.cs b/src/Machine.Specifications.Fakes/Proxy/Reflection/IProxyDefinition.cs
new file mode 100644
index 00000000..11bcafc0
--- /dev/null
+++ b/src/Machine.Specifications.Fakes/Proxy/Reflection/IProxyDefinition.cs
@@ -0,0 +1,11 @@
+using System;
+
+namespace Machine.Specifications.Fakes.Proxy.Reflection
+{
+ public interface IProxyDefinition
+ {
+ Type Type { get; }
+
+ object Create(IInterceptor interceptor, params object[] arguments);
+ }
+}
diff --git a/src/Machine.Specifications.Fakes/Proxy/Reflection/ITypeEmitter.cs b/src/Machine.Specifications.Fakes/Proxy/Reflection/ITypeEmitter.cs
new file mode 100644
index 00000000..1949845e
--- /dev/null
+++ b/src/Machine.Specifications.Fakes/Proxy/Reflection/ITypeEmitter.cs
@@ -0,0 +1,16 @@
+using System;
+using System.Reflection;
+
+namespace Machine.Specifications.Fakes.Proxy.Reflection
+{
+ public interface ITypeEmitter
+ {
+ void EmitConstructor(ConstructorInfo constructor);
+
+ void EmitInterface(Type type);
+
+ void EmitMethod(MethodInfo method);
+
+ Type CreateType();
+ }
+}
diff --git a/src/Machine.Specifications.Fakes/Proxy/Reflection/ITypeEmitterFactory.cs b/src/Machine.Specifications.Fakes/Proxy/Reflection/ITypeEmitterFactory.cs
new file mode 100644
index 00000000..85d06175
--- /dev/null
+++ b/src/Machine.Specifications.Fakes/Proxy/Reflection/ITypeEmitterFactory.cs
@@ -0,0 +1,9 @@
+using System;
+
+namespace Machine.Specifications.Fakes.Proxy.Reflection
+{
+ public interface ITypeEmitterFactory
+ {
+ ITypeEmitter DefineType(Type type);
+ }
+}
diff --git a/src/Machine.Specifications.Fakes/Proxy/Reflection/InterfaceProxyVisitor.cs b/src/Machine.Specifications.Fakes/Proxy/Reflection/InterfaceProxyVisitor.cs
new file mode 100644
index 00000000..6e72d4af
--- /dev/null
+++ b/src/Machine.Specifications.Fakes/Proxy/Reflection/InterfaceProxyVisitor.cs
@@ -0,0 +1,16 @@
+using System;
+
+namespace Machine.Specifications.Fakes.Proxy.Reflection
+{
+ public class InterfaceProxyVisitor : ProxyVisitor
+ {
+ public InterfaceProxyVisitor(Type type)
+ {
+ }
+
+ public override void VisitType(Type type)
+ {
+ base.VisitType(type);
+ }
+ }
+}
diff --git a/src/Machine.Specifications.Fakes/Proxy/Reflection/MemberInfoExtensions.cs b/src/Machine.Specifications.Fakes/Proxy/Reflection/MemberInfoExtensions.cs
new file mode 100644
index 00000000..5b978eab
--- /dev/null
+++ b/src/Machine.Specifications.Fakes/Proxy/Reflection/MemberInfoExtensions.cs
@@ -0,0 +1,34 @@
+using System;
+using System.Reflection;
+
+namespace Machine.Specifications.Fakes.Proxy.Reflection
+{
+ public static class MemberInfoExtensions
+ {
+ public static void Accept(this MemberInfo member, ProxyVisitor visitor)
+ {
+ switch (member)
+ {
+ case Type type:
+ visitor.VisitType(type);
+ break;
+
+ case ConstructorInfo constructorInfo:
+ visitor.VisitConstructor(constructorInfo);
+ break;
+
+ case PropertyInfo propertyInfo:
+ visitor.VisitProperty(propertyInfo);
+ break;
+
+ case EventInfo eventInfo:
+ visitor.VisitEvent(eventInfo);
+ break;
+
+ case MethodInfo methodInfo:
+ visitor.VisitMethod(methodInfo);
+ break;
+ }
+ }
+ }
+}
diff --git a/src/Machine.Specifications.Fakes/Proxy/Reflection/ProxyDefinition.cs b/src/Machine.Specifications.Fakes/Proxy/Reflection/ProxyDefinition.cs
new file mode 100644
index 00000000..f4c22b04
--- /dev/null
+++ b/src/Machine.Specifications.Fakes/Proxy/Reflection/ProxyDefinition.cs
@@ -0,0 +1,19 @@
+using System;
+
+namespace Machine.Specifications.Fakes.Proxy.Reflection
+{
+ public class ProxyDefinition : IProxyDefinition
+ {
+ public ProxyDefinition(Type type)
+ {
+ Type = type;
+ }
+
+ public Type Type { get; }
+
+ public object Create(IInterceptor interceptor, params object[] arguments)
+ {
+ return Activator.CreateInstance(Type, arguments);
+ }
+ }
+}
diff --git a/src/Machine.Specifications.Fakes/Proxy/Reflection/ProxyVisitor.cs b/src/Machine.Specifications.Fakes/Proxy/Reflection/ProxyVisitor.cs
new file mode 100644
index 00000000..6632ff4b
--- /dev/null
+++ b/src/Machine.Specifications.Fakes/Proxy/Reflection/ProxyVisitor.cs
@@ -0,0 +1,33 @@
+using System;
+using System.Reflection;
+
+namespace Machine.Specifications.Fakes.Proxy.Reflection
+{
+ public abstract class ProxyVisitor
+ {
+ public virtual void Visit(MemberInfo memberInfo)
+ {
+ memberInfo.Accept(this);
+ }
+
+ public virtual void VisitType(Type type)
+ {
+ }
+
+ public virtual void VisitConstructor(ConstructorInfo constructorInfo)
+ {
+ }
+
+ public virtual void VisitProperty(PropertyInfo propertyInfo)
+ {
+ }
+
+ public virtual void VisitEvent(EventInfo eventInfo)
+ {
+ }
+
+ public virtual void VisitMethod(MethodInfo methodInfo)
+ {
+ }
+ }
+}
diff --git a/src/Machine.Specifications.Fakes/Proxy/Reflection/TypeEmitter.cs b/src/Machine.Specifications.Fakes/Proxy/Reflection/TypeEmitter.cs
new file mode 100644
index 00000000..a2dc8170
--- /dev/null
+++ b/src/Machine.Specifications.Fakes/Proxy/Reflection/TypeEmitter.cs
@@ -0,0 +1,187 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Reflection.Emit;
+
+namespace Machine.Specifications.Fakes.Proxy.Reflection
+{
+ public class TypeEmitter : ITypeEmitter
+ {
+ private static readonly MethodInfo GetterMethod = typeof(MethodBase).GetMethod(
+ "GetMethodFromHandle",
+ new[] {typeof(RuntimeMethodHandle), typeof(RuntimeTypeHandle)});
+
+ private static readonly MethodInfo InterceptMethod = typeof(IInterceptor).GetMethod(
+ "Intercept",
+ new[] {typeof(IInvocation)});
+
+ private static readonly ConstructorInfo InvocationConstructor = typeof(Invocation).GetConstructor(
+ new[] {typeof(MethodInfo), typeof(object[])});
+
+ private static readonly MethodInfo ReturnProperty = typeof(Invocation).GetMethod("get_ReturnValue");
+
+ private readonly TypeBuilder typeBuilder;
+
+ private readonly FieldInfo interceptorField;
+
+ private readonly List<(MethodInfo, FieldBuilder)> fields = new List<(MethodInfo, FieldBuilder)>();
+
+ public TypeEmitter(TypeBuilder typeBuilder)
+ {
+ this.typeBuilder = typeBuilder;
+
+ interceptorField = typeBuilder.DefineField(
+ "interceptor",
+ typeof(IInterceptor),
+ FieldAttributes.Private);
+ }
+
+ public void EmitConstructor(ConstructorInfo constructor)
+ {
+ var constructorParameters = constructor
+ .GetParameters()
+ .Select(x => x.ParameterType)
+ .ToArray();
+
+ var parameters = new List { typeof(IInterceptor)};
+ parameters.AddRange(constructorParameters);
+
+ var builder = typeBuilder.DefineConstructor(
+ MethodAttributes.Public,
+ CallingConventions.Standard,
+ parameters.ToArray());
+
+ var generator = builder.GetILGenerator();
+
+ generator.Emit(OpCodes.Ldarg_0);
+
+ for (var i = 0; i < constructorParameters.Length; i++)
+ {
+ generator.Emit(OpCodes.Ldarg, i + 2);
+ }
+
+ generator.Emit(OpCodes.Call, constructor);
+
+ generator.Emit(OpCodes.Ldarg_0);
+ generator.Emit(OpCodes.Ldarg_1);
+ generator.Emit(OpCodes.Stfld, interceptorField);
+
+ generator.Emit(OpCodes.Ret);
+ }
+
+ public void EmitInterface(Type type)
+ {
+ typeBuilder.AddInterfaceImplementation(type);
+ }
+
+ public void EmitMethod(MethodInfo method)
+ {
+ var parameters = method
+ .GetParameters()
+ .Select(x => x.ParameterType)
+ .ToArray();
+
+ var methodField = typeBuilder.DefineField(
+ method.Name + "Field",
+ typeof(MethodInfo),
+ FieldAttributes.Static);
+
+ var builder = typeBuilder.DefineMethod(
+ method.Name,
+ MethodAttributes.Public | MethodAttributes.Virtual,
+ method.CallingConvention,
+ method.ReturnType,
+ parameters);
+
+ var generator = builder.GetILGenerator();
+
+ // Create arguments array
+ var arguments = generator.DeclareLocal(typeof(object).MakeArrayType());
+
+ generator.Emit(OpCodes.Ldc_I4, parameters.Length);
+ generator.Emit(OpCodes.Newarr, typeof(object));
+ generator.Emit(OpCodes.Stloc, arguments);
+
+ // Set method arguments into arguments array
+ for (var i = 0; i < parameters.Length; i++)
+ {
+ generator.Emit(OpCodes.Ldloc, arguments);
+ generator.Emit(OpCodes.Ldc_I4, i);
+ generator.Emit(OpCodes.Ldarg, i + 1);
+
+ if (parameters[i].IsValueType || parameters[i].IsGenericParameter)
+ {
+ generator.Emit(OpCodes.Box, parameters[i]);
+ }
+
+ generator.Emit(OpCodes.Stelem_Ref);
+ }
+
+ // Create new invocation object
+ var invocation = generator.DeclareLocal(typeof(Invocation));
+
+ generator.Emit(OpCodes.Ldsfld, methodField);
+ generator.Emit(OpCodes.Ldloc, arguments);
+ generator.Emit(OpCodes.Newobj, InvocationConstructor);
+ generator.Emit(OpCodes.Stloc, invocation);
+
+ // Call intercept method
+ generator.BeginExceptionBlock();
+ generator.Emit(OpCodes.Ldarg_0);
+ generator.Emit(OpCodes.Ldfld, interceptorField);
+ generator.Emit(OpCodes.Ldloc, invocation);
+ generator.Emit(OpCodes.Callvirt, InterceptMethod);
+ generator.BeginFinallyBlock();
+ // todo: set out/ref arguments
+ generator.EndExceptionBlock();
+
+ if (method.ReturnType != typeof(void))
+ {
+ generator.Emit(OpCodes.Ldloc, invocation);
+ generator.Emit(OpCodes.Callvirt, ReturnProperty);
+
+ if (method.ReturnType.IsValueType)
+ {
+ generator.Emit(OpCodes.Unbox_Any, method.ReturnType);
+ }
+ else
+ {
+ generator.Emit(OpCodes.Castclass, method.ReturnType);
+ }
+ }
+
+ generator.Emit(OpCodes.Ret);
+
+ fields.Add((method, methodField));
+ }
+
+ public Type CreateType()
+ {
+ CreateStaticConstructor();
+
+ return typeBuilder.CreateTypeInfo();
+ }
+
+ private void CreateStaticConstructor()
+ {
+ var constructor = typeBuilder.DefineConstructor(
+ MethodAttributes.Static,
+ CallingConventions.Standard,
+ Type.EmptyTypes);
+
+ var generator = constructor.GetILGenerator();
+
+ foreach (var (method, field) in fields)
+ {
+ generator.Emit(OpCodes.Ldtoken, method);
+ generator.Emit(OpCodes.Ldtoken, method.DeclaringType);
+ generator.Emit(OpCodes.Call, GetterMethod);
+ generator.Emit(OpCodes.Castclass, typeof(MethodInfo));
+ generator.Emit(OpCodes.Stsfld, field);
+ }
+
+ generator.Emit(OpCodes.Ret);
+ }
+ }
+}
diff --git a/src/Machine.Specifications.Fakes/Proxy/Reflection/TypeEmitterFactory.cs b/src/Machine.Specifications.Fakes/Proxy/Reflection/TypeEmitterFactory.cs
new file mode 100644
index 00000000..4d9fb474
--- /dev/null
+++ b/src/Machine.Specifications.Fakes/Proxy/Reflection/TypeEmitterFactory.cs
@@ -0,0 +1,36 @@
+using System;
+using System.Reflection;
+using System.Reflection.Emit;
+using System.Threading;
+
+namespace Machine.Specifications.Fakes.Proxy.Reflection
+{
+ public class TypeEmitterFactory : ITypeEmitterFactory
+ {
+ private const string Name = "Machine.Specifications.Fakes.Proxy";
+
+ private const string ModuleName = Name + ".dll";
+
+ private readonly ModuleBuilder moduleBuilder;
+
+ private int count;
+
+ public TypeEmitterFactory()
+ {
+ var name = new AssemblyName(Name);
+
+ moduleBuilder = AssemblyBuilder
+ .DefineDynamicAssembly(name, AssemblyBuilderAccess.Run)
+ .DefineDynamicModule(ModuleName);
+ }
+
+ public ITypeEmitter DefineType(Type type)
+ {
+ var value = Interlocked.Increment(ref count);
+
+ var typeBuilder = moduleBuilder.DefineType(type.Name + $"Proxy_{value}", TypeAttributes.Class);
+
+ return new TypeEmitter(typeBuilder);
+ }
+ }
+}