Skip to content

Commit

Permalink
Convert reflection abstractions GetCustomAttributes() to use _ITypeIn…
Browse files Browse the repository at this point in the history
…fo, and add ability to look up open generic attributes (e.g. MyAttribute<>) to find closed attribute instances (e.g. MyAttribute<int>)
  • Loading branch information
bradwilson committed Feb 8, 2024
1 parent d3bc65f commit 02175c7
Show file tree
Hide file tree
Showing 38 changed files with 935 additions and 305 deletions.
35 changes: 22 additions & 13 deletions src/common.tests/TestDoubles/Mocks.Attributes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,22 @@ public static _IReflectionAttributeInfo AssemblyFixtureAttribute(Type fixtureTyp
return result;
}

public static _IReflectionAttributeInfo AttributeInfo<TAttribute>()
where TAttribute : Attribute, new()
{
var customAttributes = typeof(TAttribute).GetCustomAttributesData();

// Assumption here is no constructor arguments and no named arguments. We could mock constructor arguments, but
// the named argument method is generic, and NSubtitute can't mock arbitrary generic types. Limiting ourselves
// makes this reasonable.
var result = Substitute.For<_IReflectionAttributeInfo, InterfaceProxy<_IReflectionAttributeInfo>>();
result.Attribute.Returns(new TAttribute());
result.AttributeType.Returns(Reflector.Wrap(typeof(TAttribute)));
result.GetConstructorArguments().Returns(Array.Empty<object?>());
result.GetCustomAttributes(Arg.Any<_ITypeInfo>()).Returns(callInfo => customAttributes.FindCustomAttributes(callInfo.Arg<_ITypeInfo>()));
return result;
}

public static _IReflectionAttributeInfo CollectionAttribute(string collectionName)
{
var result = Substitute.For<_IReflectionAttributeInfo, InterfaceProxy<_IReflectionAttributeInfo>>();
Expand Down Expand Up @@ -138,20 +154,13 @@ public static _IReflectionAttributeInfo DataAttribute(params ITheoryDataRow[] da
}

static IReadOnlyCollection<_IAttributeInfo> LookupAttribute(
string fullyQualifiedTypeName,
_ITypeInfo attributeTypeInfo,
_IAttributeInfo[]? attributes)
{
if (attributes is null)
return EmptyAttributeInfos;

var attributeType = Type.GetType(fullyQualifiedTypeName);
if (attributeType is null)
return EmptyAttributeInfos;

return
attributes
.Where(attribute => attributeType.IsAssignableFrom(attribute.AttributeType))
.CastOrToReadOnlyCollection();
return attributes.FindCustomAttributes(attributeTypeInfo);
}

static IReadOnlyCollection<_IReflectionAttributeInfo> LookupAttribute<TLookupType, TAttributeType>()
Expand Down Expand Up @@ -216,9 +225,9 @@ public static _IReflectionAttributeInfo TestFrameworkAttribute<TTestFrameworkAtt
var result = Substitute.For<_IReflectionAttributeInfo, InterfaceProxy<_IReflectionAttributeInfo>>();
result.Attribute.Returns(attribute);
result.AttributeType.Returns(Reflector.Wrap(typeof(TTestFrameworkAttribute)));
result.GetCustomAttributes(null!).ReturnsForAnyArgs(
result.GetCustomAttributes(Arg.Any<_ITypeInfo>()).ReturnsForAnyArgs(
callInfo => LookupAttribute(
callInfo.Arg<string>(),
callInfo.Arg<_ITypeInfo>(),
CustomAttributeData.GetCustomAttributes(attribute.GetType()).Select(x => Reflector.Wrap(x)).ToArray()
)
);
Expand Down Expand Up @@ -254,7 +263,7 @@ public static _IReflectionAttributeInfo TestFrameworkAttribute<TTestFrameworkAtt
result.Attribute.Returns(attribute);
result.AttributeType.Returns(Reflector.Wrap(typeof(TraitAttribute)));
result.GetConstructorArguments().Returns(new object[] { name, value });
result.GetCustomAttributes(typeof(TraitDiscovererAttribute)).Returns(traitDiscovererAttributes);
result.GetCustomAttributes(Arg.Any<_ITypeInfo>()).Returns(traitDiscovererAttributes);
return result;
}

Expand All @@ -267,7 +276,7 @@ public static _IReflectionAttributeInfo TraitAttribute<TTraitAttribute>()
var result = Substitute.For<_IReflectionAttributeInfo, InterfaceProxy<_IReflectionAttributeInfo>>();
result.Attribute.Returns(attribute);
result.AttributeType.Returns(Reflector.Wrap(typeof(TTraitAttribute)));
result.GetCustomAttributes(typeof(TraitDiscovererAttribute)).Returns(traitDiscovererAttributes);
result.GetCustomAttributes(Arg.Any<_ITypeInfo>()).Returns(traitDiscovererAttributes);
return result;
}

Expand Down
6 changes: 3 additions & 3 deletions src/common.tests/TestDoubles/Mocks.Reflection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public static partial class Mocks
var result = Substitute.For<_IAssemblyInfo, InterfaceProxy<_IAssemblyInfo>>();
result.AssemblyPath.Returns(assemblyFileName);
result.Name.Returns(Path.GetFileNameWithoutExtension(assemblyFileName));
result.GetCustomAttributes("").ReturnsForAnyArgs(callInfo => LookupAttribute(callInfo.Arg<string>(), attributes));
result.GetCustomAttributes(Arg.Any<_ITypeInfo>()).ReturnsForAnyArgs(callInfo => LookupAttribute(callInfo.Arg<_ITypeInfo>(), attributes));
result.GetType("").ReturnsForAnyArgs(types?.FirstOrDefault());
result.GetTypes(true).ReturnsForAnyArgs(types ?? new _ITypeInfo[0]);
return result;
Expand Down Expand Up @@ -66,7 +66,7 @@ public static partial class Mocks
result.Name.Returns(methodName);
result.ReturnType.Returns(returnType);
result.Type.Returns(type);
result.GetCustomAttributes("").ReturnsForAnyArgs(callInfo => LookupAttribute(callInfo.Arg<string>(), attributes));
result.GetCustomAttributes(Arg.Any<_ITypeInfo>()).ReturnsForAnyArgs(callInfo => LookupAttribute(callInfo.Arg<_ITypeInfo>(), attributes));
result.GetGenericArguments().Returns(genericArguments);
result.GetParameters().Returns(parameters);
// Difficult to simulate MakeGenericMethod here, so better to throw then just return null
Expand Down Expand Up @@ -116,7 +116,7 @@ public static partial class Mocks
result.IsGenericType.Returns(isGenericType);
result.IsValueType.Returns(isValueType);
result.Name.Returns(name);
result.GetCustomAttributes("").ReturnsForAnyArgs(callInfo => LookupAttribute(callInfo.Arg<string>(), attributes));
result.GetCustomAttributes(Arg.Any<_ITypeInfo>()).ReturnsForAnyArgs(callInfo => LookupAttribute(callInfo.Arg<_ITypeInfo>(), attributes));
result.GetGenericArguments().Returns(genericArguments);
result.GetMethod("", false).ReturnsForAnyArgs(callInfo => methods.FirstOrDefault(m => m.Name == callInfo.Arg<string>()));
result.GetMethods(false).ReturnsForAnyArgs(methods);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
using System;
using System.Xml;
using Xunit;
using Xunit.Internal;
using Xunit.Sdk;
using Xunit.v3;

public class ReflectionExtensionsTests
{
Expand Down Expand Up @@ -112,7 +111,7 @@ public void ForType()
Assert.True(typeof(MyEnum).IsFromLocalAssembly());
#if NETFRAMEWORK
if (!EnvironmentHelper.IsMono)
Assert.False(typeof(ConformanceLevel).IsFromLocalAssembly());
Assert.False(typeof(System.Xml.ConformanceLevel).IsFromLocalAssembly());
#endif
}

Expand All @@ -122,7 +121,7 @@ public void ForTypeInfo()
Assert.True(Reflector.Wrap(typeof(MyEnum)).IsFromLocalAssembly());
#if NETFRAMEWORK
if (!EnvironmentHelper.IsMono)
Assert.False(Reflector.Wrap(typeof(ConformanceLevel)).IsFromLocalAssembly());
Assert.False(Reflector.Wrap(typeof(System.Xml.ConformanceLevel)).IsFromLocalAssembly());
#endif
}

Expand Down

0 comments on commit 02175c7

Please sign in to comment.