Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Can't create substitute for delegate from some namespaces 🤯 #631

Closed
kislovs opened this issue Oct 30, 2020 · 7 comments · Fixed by #640
Closed

Can't create substitute for delegate from some namespaces 🤯 #631

kislovs opened this issue Oct 30, 2020 · 7 comments · Fixed by #640
Labels
bug Reported problem with NSubstitute behaviour

Comments

@kislovs
Copy link

kislovs commented Oct 30, 2020

Describe the bug
In short, I can't use Substitute.For<{delegate}>(); if this delegate from some namespaces. I discovered that namespaces started with N[a-s] led to exception, others work fine (I didn't tested any namespaces though). Also I found that it doesn't work for version 4.1.0 and later (I think it relates to #537), 4.0.0 works fine. Bug is permanently reproducable with 4.1.0+ version on different machines and operations systems. Though it works fine with Func<...> stuff.

To Reproduce

Test.csproj

<Project Sdk="Microsoft.NET.Sdk">
    <PropertyGroup>
        <TargetFramework>netcoreapp3.1</TargetFramework>

        <IsPackable>false</IsPackable>
    </PropertyGroup>

    <ItemGroup>
        <PackageReference Include="NSubstitute" Version="4.2.2" />
        <PackageReference Include="NSubstitute.Analyzers.CSharp" Version="1.0.13" />
        <PackageReference Include="NUnit" Version="3.12.0" />
        <PackageReference Include="NUnit3TestAdapter" Version="3.16.1" />
        <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
    </ItemGroup>
</Project>

Tests.cs

using NSubstitute;
using NUnit.Framework;

namespace Na { public delegate void DoNothing(); }
namespace Nb { public delegate void DoNothing(); }
namespace Nc { public delegate void DoNothing(); }
namespace Nd { public delegate void DoNothing(); }
namespace Ne { public delegate void DoNothing(); }
namespace Nf { public delegate void DoNothing(); }
namespace Ng { public delegate void DoNothing(); }
namespace Nh { public delegate void DoNothing(); }
namespace Ni { public delegate void DoNothing(); }
namespace Nj { public delegate void DoNothing(); }
namespace Nk { public delegate void DoNothing(); }
namespace Nl { public delegate void DoNothing(); }
namespace Nm { public delegate void DoNothing(); }
namespace Nn { public delegate void DoNothing(); }
namespace No { public delegate void DoNothing(); }
namespace Np { public delegate void DoNothing(); }
namespace Nq { public delegate void DoNothing(); }
namespace Nr { public delegate void DoNothing(); }
namespace Ns { public delegate void DoNothing(); }
namespace Nt { public delegate void DoNothing(); }
namespace Nu { public delegate void DoNothing(); }
namespace Nv { public delegate void DoNothing(); }
namespace Nw { public delegate void DoNothing(); }
namespace Nx { public delegate void DoNothing(); }
namespace Ny { public delegate void DoNothing(); }
namespace Nz { public delegate void DoNothing(); }

namespace Test
{
    public class Tests
    {
        [Test] public void Na() { Substitute.For<Na.DoNothing>(); }
        [Test] public void Nb() { Substitute.For<Nb.DoNothing>(); }
        [Test] public void Nc() { Substitute.For<Nc.DoNothing>(); }
        [Test] public void Nd() { Substitute.For<Nd.DoNothing>(); }
        [Test] public void Ne() { Substitute.For<Ne.DoNothing>(); }
        [Test] public void Nf() { Substitute.For<Nf.DoNothing>(); }
        [Test] public void Ng() { Substitute.For<Ng.DoNothing>(); }
        [Test] public void Nh() { Substitute.For<Nh.DoNothing>(); }
        [Test] public void Ni() { Substitute.For<Ni.DoNothing>(); }
        [Test] public void Nj() { Substitute.For<Nj.DoNothing>(); }
        [Test] public void Nk() { Substitute.For<Nk.DoNothing>(); }
        [Test] public void Nl() { Substitute.For<Nl.DoNothing>(); }
        [Test] public void Nm() { Substitute.For<Nm.DoNothing>(); }
        [Test] public void Nn() { Substitute.For<Nn.DoNothing>(); }
        [Test] public void No() { Substitute.For<No.DoNothing>(); }
        [Test] public void Np() { Substitute.For<Np.DoNothing>(); }
        [Test] public void Nq() { Substitute.For<Nq.DoNothing>(); }
        [Test] public void Nr() { Substitute.For<Nr.DoNothing>(); }
        [Test] public void Ns() { Substitute.For<Ns.DoNothing>(); }
        [Test] public void Nt() { Substitute.For<Nt.DoNothing>(); }
        [Test] public void Nu() { Substitute.For<Nu.DoNothing>(); }
        [Test] public void Nv() { Substitute.For<Nv.DoNothing>(); }
        [Test] public void Nw() { Substitute.For<Nw.DoNothing>(); }
        [Test] public void Nx() { Substitute.For<Nx.DoNothing>(); }
        [Test] public void Ny() { Substitute.For<Ny.DoNothing>(); }
        [Test] public void Nz() { Substitute.For<Nz.DoNothing>(); }
    }
}

It should results like that:
image

Full exception stacktrace:

Castle.DynamicProxy.InvalidProxyConstructorArgumentsException : Can not instantiate proxy of class: System.Object.
Could not find a parameterless constructor.
   at Castle.DynamicProxy.ProxyGenerator.CreateClassProxyInstance(Type proxyType, List`1 proxyArguments, Type classToProxy, Object[] constructorArguments)
   at Castle.DynamicProxy.ProxyGenerator.CreateClassProxy(Type classToProxy, Type[] additionalInterfacesToProxy, ProxyGenerationOptions options, Object[] constructorArguments, IInterceptor[] interceptors)
   at NSubstitute.Proxies.CastleDynamicProxy.CastleDynamicProxyFactory.CreateProxyUsingCastleProxyGenerator(Type typeToProxy, Type[] additionalInterfaces, Object[] constructorArguments, IInterceptor[] interceptors, ProxyGenerationOptions proxyGenerationOptions)
   at NSubstitute.Proxies.CastleDynamicProxy.CastleDynamicProxyFactory.GenerateDelegateProxy(ICallRouter callRouter, Type delegateType, Type[] additionalInterfaces, Object[] constructorArguments)
   at NSubstitute.Proxies.CastleDynamicProxy.CastleDynamicProxyFactory.GenerateProxy(ICallRouter callRouter, Type typeToProxy, Type[] additionalInterfaces, Object[] constructorArguments)
   at NSubstitute.Core.SubstituteFactory.Create(Type[] typesToProxy, Object[] constructorArguments, Boolean callBaseByDefault)
   at NSubstitute.Core.SubstituteFactory.Create(Type[] typesToProxy, Object[] constructorArguments)
   at NSubstitute.Substitute.For(Type[] typesToProxy, Object[] constructorArguments)
   at NSubstitute.Substitute.For[T](Object[] constructorArguments)
   at Test.Tests.Na()

Expected behaviour
Substitute should be created without any problems.

Environment:

  • NSubstitute version: 4.1.0 and later
  • NSubstitute.Analyzers version: CSharp 1.0.13
  • Platform: netcoreapp3.1, Windows / Linux

Additional context
I have no idea why it doen't works... It sounds mind-blowing if something doesn't work because of namespace 🤯

@dtchepak dtchepak added the bug Reported problem with NSubstitute behaviour label Oct 30, 2020
@dtchepak
Copy link
Member

@kislovs Thanks for raising this with such a good reproduction case, and finding the possible link to #537! Sorry for the delay in looking in to this; I haven't forgotten it and will get to it when I can. 🙇

@dtchepak
Copy link
Member

dtchepak commented Dec 6, 2020

@zvirja @alexandrnikitin I'm trying to reproduce this with DynamicProxy alone (taking NSubstitute out of the equation).

I'm probably doing something inanely silly, but I can't get this to work:

using System;
using Xunit;
using NSubstitute;

namespace Na { public delegate void DoNothing(); }
namespace Ns { public delegate void DoNothing(); }
namespace Nt { public delegate void DoNothing(); }

namespace NSubWorkshop
{
    public class Issue631_NamespaceDelegate
    {
        private class TestInterceptor : Castle.DynamicProxy.IInterceptor
        {
            public void Intercept(Castle.DynamicProxy.IInvocation invocation) { invocation.Proceed(); }
        }

        public static object Proxy<T>() where T : class {
            var proxyType = typeof(T);
            var proxyGenerationOptions = new Castle.DynamicProxy.ProxyGenerationOptions();
            var g = new Castle.DynamicProxy.ProxyGenerator();
            return g.CreateClassProxy(
                proxyType,
                new Type[0],
                proxyGenerationOptions,
                new object[0],
                new Castle.DynamicProxy.IInterceptor[] { new TestInterceptor() });
        }

        [Fact] public void Na() { Substitute.For<Na.DoNothing>(); }
        [Fact] public void Ns() { Substitute.For<Ns.DoNothing>(); }
        [Fact] public void Nt() { Substitute.For<Nt.DoNothing>(); }
        [Fact] public void DynamicProxyAction() { Proxy<Action>(); }
        [Fact] public void DynamicProxyNa() { Proxy<Na.DoNothing>(); }
        [Fact] public void DynamicProxyNt() { Proxy<Nt.DoNothing>(); }
    }
}

The Substitute.For calls are giving the error reported in this issue. The Proxy calls are failing with:

System.TypeLoadException : Could not load type 'Castle.Proxies.DoNothingProxy' from assembly 'DynamicProxyGenAssembly2, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null' because the parent type is sealed.

If you have time could you please have a quick look and point out what I'm doing wrong? 🤦

@zvirja
Copy link
Contributor

zvirja commented Dec 8, 2020

@dtchepak It's because you always need a touch of magic when you work with delegates 😊

proxyGenerationOptions.AddDelegateTypeMixin(typeof(T));

var obj = g.CreateClassProxy(
    classToProxy: typeof(object),
    additionalInterfacesToProxy: Array.Empty<Type>(),
    proxyGenerationOptions,
    constructorArguments: Array.Empty<object>(),
    new TestInterceptor());

return (T)obj.GetType().GetMethod("Invoke").CreateDelegate(typeof(T), obj);

We don't pass delegate as a type, otherwise Castle will try to generate a proxy for a subtype of our delegate type and that will fail.

@kislovs This issue seems to be fixed in the most recent version of Castle.Core library (see the first release note). Just add the following package reference to your project:

<PackageReference Include="Castle.Core" Version="4.4.1" />

Let me know if the issue is reproducible after that.

If it helps we'll just bump the version of the Castle.Core dependency to be 4.4.1.

@kislovs
Copy link
Author

kislovs commented Dec 9, 2020

Yeah, after explicit reference of Castle.Core version 4.4.1 it works fine.
image

@kislovs
Copy link
Author

kislovs commented Dec 9, 2020

Anyway, I'm not sure how it's related to castleproject/Core#469... Because even such test fails without <PackageReference Include="Castle.Core" Version="4.4.1" />

Test.csproj

<Project Sdk="Microsoft.NET.Sdk">
    <PropertyGroup>
        <TargetFramework>netcoreapp3.1</TargetFramework>

        <IsPackable>false</IsPackable>
    </PropertyGroup>

    <ItemGroup>
        <PackageReference Include="NSubstitute" Version="4.2.2" />
        <PackageReference Include="NSubstitute.Analyzers.CSharp" Version="1.0.13" />
        <PackageReference Include="NUnit" Version="3.12.0" />
        <PackageReference Include="NUnit3TestAdapter" Version="3.16.1" />
        <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
    </ItemGroup>
</Project>

Tests.cs

using NSubstitute;
using NUnit.Framework;
using No;

namespace No
{
    public delegate void DoNothing();
}

namespace Tests
{
    public class Tests
    {
        [Test]
        public void Test1()
        {
            Substitute.For<DoNothing>();
        }
    }
}

Error:

  Failed Test1 [100 ms]
  Error Message:
   Castle.DynamicProxy.InvalidProxyConstructorArgumentsException : Can not instantiate proxy of class: System.Object.
Could not find a parameterless constructor.
  Stack Trace:
     at Castle.DynamicProxy.ProxyGenerator.CreateClassProxyInstance(Type proxyType, List`1 proxyArguments, Type classToProxy, Object[] constructorArguments)
   at Castle.DynamicProxy.ProxyGenerator.CreateClassProxy(Type classToProxy, Type[] additionalInterfacesToProxy, ProxyGenerationOptions options, Object[] constructorArguments, IInterceptor[] interceptors)
   at NSubstitute.Proxies.CastleDynamicProxy.CastleDynamicProxyFactory.CreateProxyUsingCastleProxyGenerator(Type typeToProxy, Type[] additionalInterfaces, Object[] constructorArguments, IInterceptor[] interceptors, ProxyGenerationOptions proxyGenerationOptions)
   at NSubstitute.Proxies.CastleDynamicProxy.CastleDynamicProxyFactory.GenerateDelegateProxy(ICallRouter callRouter, Type delegateType, Type[] additionalInterfaces, Object[] constructorArguments)
   at NSubstitute.Proxies.CastleDynamicProxy.CastleDynamicProxyFactory.GenerateProxy(ICallRouter callRouter, Type typeToProxy, Type[] additionalInterfaces, Object[] constructorArguments)
   at NSubstitute.Core.SubstituteFactory.Create(Type[] typesToProxy, Object[] constructorArguments, Boolean callBaseByDefault)
   at NSubstitute.Core.SubstituteFactory.Create(Type[] typesToProxy, Object[] constructorArguments)
   at NSubstitute.Substitute.For(Type[] typesToProxy, Object[] constructorArguments)
   at NSubstitute.Substitute.For[T](Object[] constructorArguments)
   at Tests.Tests.Test1() in C:\workspace\sandbox\Sandbox\Test\Tests.cs:line 17

I just don't understand error Could not find a parameterless constructor for System.Object. Does it have constructors with parameters at all?

@zvirja
Copy link
Contributor

zvirja commented Dec 9, 2020

@kislovs It's indeed some weird stuff 😆 Interestingly, it happens only on certain versions of .NET Core (e.g. cannot repro it on .NET 5).

To troubleshoot why it happens we should debug Caslte.Core and see what happens under the hood. I don't have much time for that, but if you are curious you could solve that mystery 😊

I will create a PR soon to pin [4.1.1] of the Castle.Core as it seems to fix the issue.

@kislovs
Copy link
Author

kislovs commented Dec 9, 2020

if you are curious you could solve that mystery

I'm not so interesting into such mystery. I just found some weird thing during usual test writing and report it 🙂 Nice that it was fixed without long researching.

dtchepak added a commit to dtchepak/NSubstitute that referenced this issue Jan 11, 2021
Closes nsubstitute#631.

Misc changes:
- ignore .ionide files
- helper to run test using only dotnet core (without .NET framework).
dtchepak added a commit that referenced this issue Jan 12, 2021
Closes #631.

Misc changes:
- ignore .ionide files
- helper to run test using only dotnet core (without .NET framework).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Reported problem with NSubstitute behaviour
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants