This repository was archived by the owner on Jun 30, 2023. It is now read-only.
-
-
Notifications
You must be signed in to change notification settings - Fork 75
Switch to Castle dynamic proxy by default, simplify everything #41
Merged
Merged
Changes from 1 commit
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,63 @@ | ||
| using System; | ||
| using System.Collections.ObjectModel; | ||
| using System.Linq; | ||
| using System.Reflection; | ||
| using System.Threading; | ||
| using Castle.DynamicProxy; | ||
| using Stunts; | ||
| using Stunts.Sdk; | ||
|
|
||
| namespace Moq.Sdk | ||
| { | ||
| /// <summary> | ||
| /// Provides an <see cref="IMockFactory"/> that creates types at run-time using Castle DynamicProxy. | ||
| /// </summary> | ||
| public class DynamicMockFactory : DynamicStuntFactory, IMockFactory | ||
| { | ||
| /// <inheritdoc /> | ||
| public object CreateMock(Assembly mocksAssembly, Type baseType, Type[] implementedInterfaces, object[] constructorArguments) | ||
| => CreateStunt(mocksAssembly, baseType, implementedInterfaces, constructorArguments); | ||
|
|
||
| /// <summary> | ||
| /// Creates the mock proxy. | ||
| /// </summary> | ||
| protected override object CreateProxy(Type baseType, Type[] implementedInterfaces, object[] constructorArguments, bool notImplemented) | ||
| { | ||
| if (!implementedInterfaces.Contains(typeof(IMocked))) | ||
| { | ||
| var fixedInterfaces = new Type[implementedInterfaces.Length + 1]; | ||
| fixedInterfaces[0] = typeof(IMocked); | ||
| implementedInterfaces.CopyTo(fixedInterfaces, 1); | ||
| implementedInterfaces = fixedInterfaces; | ||
| } | ||
|
|
||
| var mocked = (IMocked)Generator.CreateClassProxy(baseType, implementedInterfaces, Options, constructorArguments, new DynamicMockInterceptor(notImplemented)); | ||
|
|
||
| // Save for cloning purposes. We opened a generated proxy from DP to figure out the ctor signature it creates. | ||
| // The lazy-calculated value allows us to provide a new interceptor for every retrieval. | ||
| // Add first-class support in statebag for this pattern of either Func<T> for values, or | ||
| // Lazy<T>, since both could be quite useful for expensive state that may be needed lazily. | ||
| mocked.Mock.State.Set(".ctor", () => new object[] { new IInterceptor[] { new DynamicMockInterceptor(notImplemented) } }.Concat(constructorArguments).ToArray()); | ||
|
|
||
| return mocked; | ||
| } | ||
|
|
||
| class DynamicMockInterceptor : DynamicStuntInterceptor | ||
| { | ||
| IMock mock; | ||
|
|
||
| public DynamicMockInterceptor(bool notImplemented) : base(notImplemented) { } | ||
|
|
||
| public override void Intercept(IInvocation invocation) | ||
| { | ||
| if (invocation.Method.DeclaringType == typeof(IMocked)) | ||
| { | ||
| invocation.ReturnValue = LazyInitializer.EnsureInitialized(ref mock, () => new DefaultMock((IStunt)invocation.Proxy)); | ||
| return; | ||
| } | ||
|
|
||
| base.Intercept(invocation); | ||
| } | ||
| } | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| <Project Sdk="Microsoft.NET.Sdk"> | ||
|
|
||
| <PropertyGroup> | ||
| <TargetFramework>netstandard2.0</TargetFramework> | ||
| <RootNamespace>Moq.Sdk</RootNamespace> | ||
| </PropertyGroup> | ||
|
|
||
| <ItemGroup> | ||
| <ProjectReference Include="..\..\Moq\Moq.Sdk\Moq.Sdk.csproj" /> | ||
| <ProjectReference Include="..\Stunts.DynamicProxy\Stunts.DynamicProxy.csproj" /> | ||
| </ItemGroup> | ||
|
|
||
| </Project> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| using System.Runtime.CompilerServices; | ||
|
|
||
| [assembly: InternalsVisibleTo("Moq.DynamicProxy")] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,80 @@ | ||
| using System; | ||
| using System.Linq; | ||
| using System.Reflection; | ||
| using System.Security.Permissions; | ||
| using Castle.DynamicProxy; | ||
| using Castle.DynamicProxy.Generators; | ||
|
|
||
| namespace Stunts.Sdk | ||
| { | ||
| /// <summary> | ||
| /// Provides a <see cref="IStuntFactory"/> that creates types at run-time using Castle DynamicProxy. | ||
| /// </summary> | ||
| public class DynamicStuntFactory : IStuntFactory | ||
| { | ||
| static readonly ProxyGenerator generator; | ||
| static readonly ProxyGenerationOptions options; | ||
|
|
||
| static DynamicStuntFactory() | ||
| { | ||
| #pragma warning disable 618 | ||
| AttributesToAvoidReplicating.Add<SecurityPermissionAttribute>(); | ||
| #pragma warning restore 618 | ||
|
|
||
| AttributesToAvoidReplicating.Add<System.Runtime.InteropServices.MarshalAsAttribute>(); | ||
| AttributesToAvoidReplicating.Add<System.Runtime.InteropServices.TypeIdentifierAttribute>(); | ||
|
|
||
| options = new ProxyGenerationOptions { Hook = new ToStringMethodHook() }; | ||
| #if DEBUG | ||
| // This allows invoking generator.ProxyBuilder.ModuleScope.SaveAssembly() for troubleshooting. | ||
| generator = new ProxyGenerator(new DefaultProxyBuilder(new ModuleScope(true))); | ||
| #else | ||
| generator = new ProxyGenerator(); | ||
| #endif | ||
| } | ||
|
|
||
| /// <inheritdoc /> | ||
| public object CreateStunt(Assembly stuntsAssembly, Type baseType, Type[] implementedInterfaces, object[] constructorArguments) | ||
| { | ||
| var notImplemented = false; | ||
|
|
||
| if (baseType.IsInterface) | ||
| { | ||
| var fixedInterfaces = new Type[implementedInterfaces.Length + 1]; | ||
| fixedInterfaces[0] = baseType; | ||
| implementedInterfaces.CopyTo(fixedInterfaces, 1); | ||
| implementedInterfaces = fixedInterfaces; | ||
| baseType = typeof(object); | ||
| notImplemented = true; | ||
| } | ||
|
|
||
| if (!implementedInterfaces.Contains(typeof(IStunt))) | ||
| { | ||
| var fixedInterfaces = new Type[implementedInterfaces.Length + 1]; | ||
| fixedInterfaces[0] = typeof(IStunt); | ||
| implementedInterfaces.CopyTo(fixedInterfaces, 1); | ||
| implementedInterfaces = fixedInterfaces; | ||
| } | ||
|
|
||
| // TODO: do delegate proxies via interfaces like moq4 does. | ||
stakx marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| return CreateProxy(baseType, implementedInterfaces, constructorArguments, notImplemented); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Creates the proxy with the <see cref="Generator"/>, adding interceptors to implement its behavior. | ||
| /// </summary> | ||
| protected virtual object CreateProxy(Type baseType, Type[] implementedInterfaces, object[] constructorArguments, bool notImplemented) | ||
| => generator.CreateClassProxy(baseType, implementedInterfaces, options, constructorArguments, new DynamicStuntInterceptor(notImplemented)); | ||
|
|
||
| /// <summary> | ||
| /// The <see cref="ProxyGenerator"/> used to create proxy types. | ||
| /// </summary> | ||
| protected ProxyGenerator Generator => generator; | ||
|
|
||
| /// <summary> | ||
| /// The <see cref="ProxyGenerationOptions"/> used when creating proxy types. | ||
| /// </summary> | ||
| protected ProxyGenerationOptions Options => options; | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,58 @@ | ||
| using System; | ||
| using System.Collections.ObjectModel; | ||
| using Castle.DynamicProxy; | ||
|
|
||
| namespace Stunts.Sdk | ||
| { | ||
| internal class DynamicStuntInterceptor : IInterceptor, IStunt // Implemented to detect breaking changes in Stunts | ||
| { | ||
| readonly bool notImplemented; | ||
| readonly BehaviorPipeline pipeline; | ||
|
|
||
| internal DynamicStuntInterceptor(bool notImplemented) | ||
| { | ||
| this.notImplemented = notImplemented; | ||
| pipeline = new BehaviorPipeline(); | ||
| } | ||
|
|
||
| public ObservableCollection<IStuntBehavior> Behaviors => pipeline.Behaviors; | ||
|
|
||
| public virtual void Intercept(IInvocation invocation) | ||
| { | ||
| if (invocation.Method.DeclaringType == typeof(IStunt)) | ||
| { | ||
| invocation.ReturnValue = Behaviors; | ||
| return; | ||
| } | ||
|
|
||
| var input = new MethodInvocation(invocation.Proxy, invocation.Method, invocation.Arguments); | ||
| var returns = pipeline.Invoke(input, (i, next) => { | ||
| try | ||
| { | ||
| if (notImplemented) | ||
| throw new NotImplementedException(); | ||
|
|
||
| invocation.Proceed(); | ||
| var returnValue = invocation.ReturnValue; | ||
| return input.CreateValueReturn(returnValue, invocation.Arguments); | ||
| } | ||
| catch (Exception ex) | ||
| { | ||
| return input.CreateExceptionReturn(ex); | ||
| } | ||
| }); | ||
|
|
||
| var exception = returns.Exception; | ||
| if (exception != null) | ||
| throw exception; | ||
|
|
||
| invocation.ReturnValue = returns.ReturnValue; | ||
| for (var i = 0; i < returns.Outputs.Count; i++) | ||
| { | ||
| var name = returns.Outputs.GetInfo(i).Name; | ||
| var index = input.Arguments.IndexOf(name); | ||
| invocation.SetArgumentValue(index, returns.Outputs[i]); | ||
| } | ||
| } | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| <Project Sdk="Microsoft.NET.Sdk"> | ||
|
|
||
| <PropertyGroup> | ||
| <TargetFramework>netstandard2.0</TargetFramework> | ||
| <RootNamespace>Stunts.Sdk</RootNamespace> | ||
| </PropertyGroup> | ||
|
|
||
| <ItemGroup> | ||
| <PackageReference Include="Castle.Core" Version="4.4.0" /> | ||
| </ItemGroup> | ||
|
|
||
| <ItemGroup> | ||
| <ProjectReference Include="..\..\Stunts\Stunts\Stunts.csproj" /> | ||
| </ItemGroup> | ||
|
|
||
| </Project> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| using System; | ||
| using System.Reflection; | ||
| using Castle.DynamicProxy; | ||
|
|
||
| namespace Stunts.Sdk | ||
| { | ||
| /// <summary> | ||
| /// Hook used to tells Castle which methods to proxy in mocked classes. | ||
| /// | ||
| /// Here we proxy the default methods Castle suggests (everything Object's methods) | ||
| /// plus Object.ToString(), so we can give mocks useful default names. | ||
| /// | ||
| /// This is required to allow Moq to mock ToString on proxy *class* implementations. | ||
| /// </summary> | ||
| [Serializable] | ||
| internal class ToStringMethodHook : AllMethodsHook | ||
| { | ||
| /// <summary> | ||
| /// Extends AllMethodsHook.ShouldInterceptMethod to also intercept Object.ToString(). | ||
| /// </summary> | ||
| public override bool ShouldInterceptMethod(Type type, MethodInfo methodInfo) | ||
| { | ||
| var isObjectToString = methodInfo.DeclaringType == typeof(object) && methodInfo.Name == "ToString"; | ||
|
|
||
| return base.ShouldInterceptMethod(type, methodInfo) || isObjectToString; | ||
| } | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.