diff --git a/Source/Extensions.cs b/Source/Extensions.cs index 588edcbf3..038a584af 100644 --- a/Source/Extensions.cs +++ b/Source/Extensions.cs @@ -165,34 +165,6 @@ public static bool IsMockeable(this Type typeToMock) return typeToMock.GetTypeInfo().IsInterface || typeToMock.GetTypeInfo().IsAbstract || typeToMock.IsDelegate() || (typeToMock.GetTypeInfo().IsClass && !typeToMock.GetTypeInfo().IsSealed); } - public static bool IsSerializableMockable(this Type typeToMock) - { - return typeToMock.ContainsDeserializationConstructor() && typeToMock.IsGetObjectDataVirtual(); - } - - private static bool IsGetObjectDataVirtual(this Type typeToMock) - { -#if NETCORE - return false; -#else - var getObjectDataMethod = typeToMock.GetInterfaceMap(typeof (ISerializable)).TargetMethods[0]; - return !getObjectDataMethod.IsPrivate && getObjectDataMethod.IsVirtual && !getObjectDataMethod.IsFinal; -#endif - } - - private static bool ContainsDeserializationConstructor(this Type typeToMock) - { -#if NETCORE - return false; -#else - return typeToMock.GetConstructor( - BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, - null, - new[] {typeof (SerializationInfo), typeof (StreamingContext)}, - null) != null; -#endif - } - public static bool CanOverride(this MethodBase method) { return method.IsVirtual && !method.IsFinal && !method.IsPrivate; diff --git a/Source/Mock.cs b/Source/Mock.cs index c3cebc1a1..3bff685a4 100644 --- a/Source/Mock.cs +++ b/Source/Mock.cs @@ -746,13 +746,15 @@ private static object GetInitialValue(IDefaultValueProvider valueProvider, Stack // to deal with loops in the property graph valueProvider = new EmptyDefaultValueProvider(); } +#if !NETCORE else { - // to make sure that properties of types that don't impelemt ISerializable properly (Castle throws ArgumentException) + // to make sure that properties of types that don't implement ISerializable properly (Castle throws ArgumentException) // are mocked with default value instead. // It will only result in exception if the properties are accessed. valueProvider = new SerializableTypesValueProvider(valueProvider); } +#endif return valueProvider.ProvideDefault(property.GetGetMethod()); } diff --git a/Source/SerializableTypesValueProvider.cs b/Source/SerializableTypesValueProvider.cs index b23494669..8a1aaf4e0 100644 --- a/Source/SerializableTypesValueProvider.cs +++ b/Source/SerializableTypesValueProvider.cs @@ -1,23 +1,16 @@ -using System.Reflection; -#if !NETCORE +#if !NETCORE + +using System; +using System.Reflection; using System.Runtime.Serialization; -#endif namespace Moq { -#if !NETCORE /// /// A that returns an empty default value /// for serializable types that do not implement properly, /// and returns the value provided by the decorated provider otherwise. /// -#else - /// - /// A that returns an empty default value - /// for serializable types that do not implement ISerializable properly, - /// and returns the value provided by the decorated provider otherwise. - /// -#endif internal class SerializableTypesValueProvider : IDefaultValueProvider { private readonly IDefaultValueProvider decorated; @@ -35,9 +28,33 @@ public void DefineDefault(T value) public object ProvideDefault(MethodInfo member) { - return !member.ReturnType.GetTypeInfo().IsSerializable || member.ReturnType.IsSerializableMockable() - ? decorated.ProvideDefault(member) - : emptyDefaultValueProvider.ProvideDefault(member); + return IsSerializableWithIncorrectImplementationForISerializable(member.ReturnType) + ? emptyDefaultValueProvider.ProvideDefault(member) + : decorated.ProvideDefault(member); + } + + private static bool IsSerializableWithIncorrectImplementationForISerializable(Type typeToMock) + { + return typeToMock.IsSerializable + && typeof(ISerializable).IsAssignableFrom(typeToMock) + && !(ContainsDeserializationConstructor(typeToMock) && IsGetObjectDataVirtual(typeToMock)); + } + + private static bool ContainsDeserializationConstructor(Type typeToMock) + { + return typeToMock.GetConstructor( + BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, + null, + new[] { typeof(SerializationInfo), typeof(StreamingContext) }, + null) != null; + } + + private static bool IsGetObjectDataVirtual(Type typeToMock) + { + var getObjectDataMethod = typeToMock.GetInterfaceMap(typeof(ISerializable)).TargetMethods[0]; + return !getObjectDataMethod.IsPrivate && getObjectDataMethod.IsVirtual && !getObjectDataMethod.IsFinal; } } } + +#endif diff --git a/UnitTests/Regressions/IssueReportsFixture.cs b/UnitTests/Regressions/IssueReportsFixture.cs index 9bd2961e1..b071e32fe 100644 --- a/UnitTests/Regressions/IssueReportsFixture.cs +++ b/UnitTests/Regressions/IssueReportsFixture.cs @@ -8,6 +8,7 @@ using System.Reflection; using System.Text; using System.Text.RegularExpressions; +using Castle.DynamicProxy; using Moq; using Moq.Properties; using Moq.Protected; @@ -15,6 +16,7 @@ #if !NETCORE using System.Web.UI.HtmlControls; +using System.Runtime.Serialization; #endif using System.Threading; using System.Threading.Tasks; @@ -168,6 +170,160 @@ public void DoTest() #endif #endregion + #region 163 + +#if !NETCORE + public class Issue163 // see also issue 340 below + { + [Fact] + public void WhenMoqEncountersTypeThatDynamicProxyCannotHandleFallbackToEmptyDefaultValue() + { + // First, establish that we're looking at situation involving a type that DynamicProxy + // cannot handle: + var proxyGenerator = new ProxyGenerator(); + Assert.Throws(() => proxyGenerator.CreateClassProxy()); + + // With such a type, Moq should fall back to the empty default value provider: + var foo = Mock.Of(); + Assert.Null(foo.SomeSerializableObject); + } + + public abstract class Foo + { + public abstract NoDeserializationCtor SomeSerializableObject { get; } + } + + [Serializable] + public abstract class NoDeserializationCtor : ISerializable + { + public virtual void GetObjectData(SerializationInfo info, StreamingContext context) { } + } + + /// + /// The tests in this class document some of Moq's assumptions about + /// how Castle DynamicProxy handles various correct and incorrect + /// implementations of `ISerializable`. If any one these tests start + /// failing, this is a signal that DynamicProxy has changed, and that + /// Moq might have to be adjusted accordingly. + /// + public class AssumptionsAboutDynamicProxy + { + [Theory] + [InlineData(typeof(CorrectImplementation))] + public void CanCreateProxyForCorrectImplementaiton(Type classToProxy) + { + var proxyGenerator = new ProxyGenerator(); + var proxy = proxyGenerator.CreateClassProxy(classToProxy); + Assert.NotNull(proxy); + } + + [Theory] + [InlineData(typeof(NoSerializableAttribute))] + [InlineData(typeof(NoSerializableAttributeAndNoDeserializationCtor))] + [InlineData(typeof(NoSerializableAttributeAndGetObjectDataNotVirtual))] + public void DoesNotMindPossibleErrorsIfNoSerializableAttribute(Type classToProxy) + { + var proxyGenerator = new ProxyGenerator(); + var proxy = proxyGenerator.CreateClassProxy(classToProxy); + Assert.NotNull(proxy); + } + + [Theory] + [InlineData(typeof(NotISerializable))] + [InlineData(typeof(NotISerializableAndNoDeserializationCtor))] + [InlineData(typeof(NotISerializableAndGetObjectDataNotVirtual))] + public void DoesNotMindPossibleErrorsIfNotISerializable(Type classToProxy) + { + var proxyGenerator = new ProxyGenerator(); + var proxy = proxyGenerator.CreateClassProxy(classToProxy); + Assert.NotNull(proxy); + } + + + [Theory] + [InlineData(typeof(NoDeserializationCtor))] + public void DoesMindMissingDeserializationCtor(Type classToProxy) + { + var proxyGenerator = new ProxyGenerator(); + Assert.Throws(() => proxyGenerator.CreateClassProxy(classToProxy)); + } + + [Theory] + [InlineData(typeof(GetObjectDataNotVirtual))] + public void DoesMindNonVirtualGetObjectData(Type classToProxy) + { + var proxyGenerator = new ProxyGenerator(); + Assert.Throws(() => proxyGenerator.CreateClassProxy(classToProxy)); + } + + public abstract class NoSerializableAttribute : ISerializable + { + protected NoSerializableAttribute() { } + protected NoSerializableAttribute(SerializationInfo info, StreamingContext context) { } + public void GetObjectData(SerializationInfo info, StreamingContext context) { } + } + + public abstract class NoSerializableAttributeAndNoDeserializationCtor : ISerializable + { + public void GetObjectData(SerializationInfo info, StreamingContext context) { } + } + + public abstract class NoSerializableAttributeAndGetObjectDataNotVirtual : ISerializable + { + protected NoSerializableAttributeAndGetObjectDataNotVirtual() { } + protected NoSerializableAttributeAndGetObjectDataNotVirtual(SerializationInfo info, StreamingContext context) { } + public virtual void GetObjectData(SerializationInfo info, StreamingContext context) { } + } + + [Serializable] + public abstract class CorrectImplementation : ISerializable + { + protected CorrectImplementation() { } + protected CorrectImplementation(SerializationInfo info, StreamingContext context) { } + public virtual void GetObjectData(SerializationInfo info, StreamingContext context) { } + } + + [Serializable] + public abstract class NotISerializable + { + protected NotISerializable() { } + protected NotISerializable(SerializationInfo info, StreamingContext context) { } + public void GetObjectData(SerializationInfo info, StreamingContext context) { } + } + + [Serializable] + public abstract class NotISerializableAndNoDeserializationCtor + { + public virtual void GetObjectData(SerializationInfo info, StreamingContext context) { } + } + + [Serializable] + public abstract class NotISerializableAndGetObjectDataNotVirtual + { + protected NotISerializableAndGetObjectDataNotVirtual() { } + protected NotISerializableAndGetObjectDataNotVirtual(SerializationInfo info, StreamingContext context) { } + public virtual void GetObjectData(SerializationInfo info, StreamingContext context) { } + } + + [Serializable] + public abstract class NoDeserializationCtor : ISerializable + { + public virtual void GetObjectData(SerializationInfo info, StreamingContext context) { } + } + + [Serializable] + public abstract class GetObjectDataNotVirtual : ISerializable + { + protected GetObjectDataNotVirtual() { } + protected GetObjectDataNotVirtual(SerializationInfo info, StreamingContext context) { } + public void GetObjectData(SerializationInfo info, StreamingContext context) { } + } + } + } +#endif + + #endregion + #region #176 public class Issue176 @@ -478,6 +634,51 @@ public void Strict_mock_expecting_calls_with_nonequal_BadlyHashed_values_should_ #endregion // #328 + #region 340 + +#if !NETCORE + /// + /// These tests check whether the presence of a deserialization ctor and/or a GetObjectData + /// method alone can fool Moq into assuming that a type is ISerializable, or implements + /// it incompletely when it isn't ISerializable at all. + /// + public class Issue340 // see also issue 163 above + { + [Fact] + public void ClaimsPrincipal_has_ISerializable_contract_but_is_not_ISerializable() + { + var ex = Record.Exception(() => Mock.Of()); + Assert.Null(ex); + } + + public abstract class Repro1 + { + public abstract System.Security.Claims.ClaimsPrincipal Principal { get; } + } + + [Fact] + public void Foo_has_incomplete_ISerializable_contract_but_is_not_ISerializable() + { + var ex = Record.Exception(() => Mock.Of()); + Assert.Null(ex); + } + + public abstract class Repro2 + { + public abstract Foo FooProperty { get; } + } + + [Serializable] + public class Foo + { + public Foo() { } + protected Foo(SerializationInfo info, StreamingContext context) { } + } + } +#endif + +#endregion + // Old @ Google Code #region #47