Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface;
#if NETFRAMEWORK
using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Utilities;
#endif

namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices;

Expand All @@ -24,10 +21,10 @@ internal sealed class ReflectionOperations : IReflectionOperations
[return: NotNullIfNotNull(nameof(memberInfo))]
public object[]? GetCustomAttributes(MemberInfo memberInfo)
#if NETFRAMEWORK
=> [.. ReflectionUtility.GetCustomAttributes(memberInfo)];
=> [.. GetCustomAttributesCore(memberInfo, type: null)];
#else
{
object[] attributes = memberInfo.GetCustomAttributes(typeof(Attribute), inherit: true);
object[] attributes = memberInfo.GetCustomAttributes(inherit: true);

// Ensures that when the return of this method is used here:
// https://github.com/microsoft/testfx/blob/e101a9d48773cc935c7b536d25d378d9a3211fee/src/Adapter/MSTest.TestAdapter/Helpers/ReflectHelper.cs#L461
Expand All @@ -47,7 +44,7 @@ internal sealed class ReflectionOperations : IReflectionOperations
[return: NotNullIfNotNull(nameof(memberInfo))]
public object[]? GetCustomAttributes(MemberInfo memberInfo, Type type) =>
#if NETFRAMEWORK
[.. ReflectionUtility.GetCustomAttributesCore(memberInfo, type)];
[.. GetCustomAttributesCore(memberInfo, type)];
#else
memberInfo.GetCustomAttributes(type, inherit: true);
#endif
Expand All @@ -60,7 +57,7 @@ internal sealed class ReflectionOperations : IReflectionOperations
/// <returns> The list of attributes of the given type on the member. Empty list if none found. </returns>
public object[] GetCustomAttributes(Assembly assembly, Type type) =>
#if NETFRAMEWORK
ReflectionUtility.GetCustomAttributes(assembly, type).ToArray();
GetCustomAttributesForAssembly(assembly, type).ToArray();
#else
assembly.GetCustomAttributes(type, inherit: true);
#endif
Expand Down Expand Up @@ -106,4 +103,254 @@ public MethodInfo[] GetRuntimeMethods(Type type)
#pragma warning restore IL2026 // Members attributed with RequiresUnreferencedCode may break when trimming
#pragma warning restore IL2067 // 'target parameter' argument does not satisfy 'DynamicallyAccessedMembersAttribute' in call to 'target method'.
#pragma warning restore IL2057 // Unrecognized value passed to the typeName parameter of 'System.Type.GetType(String)'

#if NETFRAMEWORK
/// <summary>
/// Get custom attributes on a member for both normal and reflection only load.
/// </summary>
/// <param name="memberInfo">Member for which attributes needs to be retrieved.</param>
/// <param name="type">Type of attribute to retrieve.</param>
/// <returns>All attributes of give type on member.</returns>
#pragma warning disable CA1859 // Use concrete types when possible for improved performance
private static IReadOnlyList<object> GetCustomAttributesCore(MemberInfo memberInfo, Type? type)
#pragma warning restore CA1859
{
bool shouldGetAllAttributes = type is null;

if (!IsReflectionOnlyLoad(memberInfo))
{
return shouldGetAllAttributes ? memberInfo.GetCustomAttributes(inherit: true) : memberInfo.GetCustomAttributes(type, inherit: true);
}
else
{
List<object> nonUniqueAttributes = [];
Dictionary<string, object> uniqueAttributes = [];

int inheritanceThreshold = 10;
int inheritanceLevel = 0;

if (memberInfo.MemberType == MemberTypes.TypeInfo)
{
// This code is based on the code for fetching CustomAttributes in System.Reflection.CustomAttribute(RuntimeType type, RuntimeType caType, bool inherit)
var tempTypeInfo = memberInfo as TypeInfo;

do
{
IList<CustomAttributeData> attributes = CustomAttributeData.GetCustomAttributes(tempTypeInfo);
AddNewAttributes(
attributes,
shouldGetAllAttributes,
type!,
uniqueAttributes,
nonUniqueAttributes);
tempTypeInfo = tempTypeInfo!.BaseType?.GetTypeInfo();
inheritanceLevel++;
}
while (tempTypeInfo is not null && tempTypeInfo != typeof(object).GetTypeInfo()
&& inheritanceLevel < inheritanceThreshold);
}
else if (memberInfo.MemberType == MemberTypes.Method)
{
// This code is based on the code for fetching CustomAttributes in System.Reflection.CustomAttribute(RuntimeMethodInfo method, RuntimeType caType, bool inherit).
var tempMethodInfo = memberInfo as MethodInfo;

do
{
IList<CustomAttributeData> attributes = CustomAttributeData.GetCustomAttributes(tempMethodInfo);
AddNewAttributes(
attributes,
shouldGetAllAttributes,
type!,
uniqueAttributes,
nonUniqueAttributes);
MethodInfo? baseDefinition = tempMethodInfo!.GetBaseDefinition();

if (baseDefinition is not null
&& string.Equals(
string.Concat(tempMethodInfo.DeclaringType.FullName, tempMethodInfo.Name),
string.Concat(baseDefinition.DeclaringType.FullName, baseDefinition.Name), StringComparison.Ordinal))
{
break;
}

tempMethodInfo = baseDefinition;
inheritanceLevel++;
}
while (tempMethodInfo is not null && inheritanceLevel < inheritanceThreshold);
}
else
{
// Ideally we should not be reaching here. We only query for attributes on types/methods currently.
// Return the attributes that CustomAttributeData returns in this cases not considering inheritance.
IList<CustomAttributeData> firstLevelAttributes =
CustomAttributeData.GetCustomAttributes(memberInfo);
AddNewAttributes(firstLevelAttributes, shouldGetAllAttributes, type!, uniqueAttributes, nonUniqueAttributes);
}

nonUniqueAttributes.AddRange(uniqueAttributes.Values);
return nonUniqueAttributes;
}
}

private static List<Attribute> GetCustomAttributesForAssembly(Assembly assembly, Type type)
{
if (!assembly.ReflectionOnly)
{
return [.. assembly.GetCustomAttributes(type)];
}

List<CustomAttributeData> customAttributes = [.. CustomAttributeData.GetCustomAttributes(assembly)];

List<Attribute> attributesArray = [];

foreach (CustomAttributeData attribute in customAttributes)
{
if (!IsTypeInheriting(attribute.Constructor.DeclaringType, type)
&& !attribute.Constructor.DeclaringType.AssemblyQualifiedName.Equals(
type.AssemblyQualifiedName, StringComparison.Ordinal))
{
continue;
}

Attribute? attributeInstance = CreateAttributeInstance(attribute);
if (attributeInstance is not null)
{
attributesArray.Add(attributeInstance);
}
}

return attributesArray;
}

/// <summary>
/// Create instance of the attribute for reflection only load.
/// </summary>
/// <param name="attributeData">The attribute data.</param>
/// <returns>An attribute.</returns>
private static Attribute? CreateAttributeInstance(CustomAttributeData attributeData)
{
object? attribute = null;
try
{
// Create instance of attribute. For some case, constructor param is returned as ReadOnlyCollection
// instead of array. So convert it to array else constructor invoke will fail.
var attributeType = Type.GetType(attributeData.Constructor.DeclaringType.AssemblyQualifiedName);

List<Type> constructorParameters = [];
List<object> constructorArguments = [];
foreach (CustomAttributeTypedArgument parameter in attributeData.ConstructorArguments)
{
var parameterType = Type.GetType(parameter.ArgumentType.AssemblyQualifiedName);
constructorParameters.Add(parameterType);
if (!parameterType.IsArray
|| parameter.Value is not IEnumerable enumerable)
{
constructorArguments.Add(parameter.Value);
continue;
}

ArrayList list = [];
foreach (object? item in enumerable)
{
if (item is CustomAttributeTypedArgument argument)
{
list.Add(argument.Value);
}
else
{
list.Add(item);
}
}

constructorArguments.Add(list.ToArray(parameterType.GetElementType()));
}

ConstructorInfo constructor = attributeType.GetConstructor([.. constructorParameters]);
attribute = constructor.Invoke([.. constructorArguments]);

foreach (CustomAttributeNamedArgument namedArgument in attributeData.NamedArguments)
{
attributeType.GetProperty(namedArgument.MemberInfo.Name).SetValue(attribute, namedArgument.TypedValue.Value, null);
}
}

// If not able to create instance of attribute ignore attribute. (May happen for custom user defined attributes).
catch (BadImageFormatException)
{
}
catch (FileLoadException)
{
}
catch (TypeLoadException)
{
}

return attribute as Attribute;
}

private static void AddNewAttributes(
IList<CustomAttributeData> customAttributes,
bool shouldGetAllAttributes,
Type type,
Dictionary<string, object> uniqueAttributes,
List<object> nonUniqueAttributes)
{
foreach (CustomAttributeData attribute in customAttributes)
{
if (!shouldGetAllAttributes
&& !IsTypeInheriting(attribute.Constructor.DeclaringType, type)
&& !attribute.Constructor.DeclaringType.AssemblyQualifiedName.Equals(
type.AssemblyQualifiedName, StringComparison.Ordinal))
{
continue;
}

Attribute? attributeInstance = CreateAttributeInstance(attribute);
if (attributeInstance is null)
{
continue;
}

Type attributeType = attributeInstance.GetType();
IReadOnlyList<object> attributeUsageAttributes = GetCustomAttributesCore(
attributeType,
typeof(AttributeUsageAttribute));
if (attributeUsageAttributes.Count > 0
&& attributeUsageAttributes[0] is AttributeUsageAttribute { AllowMultiple: false })
{
if (!uniqueAttributes.ContainsKey(attributeType.FullName))
{
uniqueAttributes.Add(attributeType.FullName, attributeInstance);
}
}
else
{
nonUniqueAttributes.Add(attributeInstance);
}
}
}

/// <summary>
/// Check whether the member is loaded in a reflection only context.
/// </summary>
/// <param name="memberInfo"> The member Info. </param>
/// <returns> True if the member is loaded in a reflection only context. </returns>
private static bool IsReflectionOnlyLoad(MemberInfo? memberInfo)
=> memberInfo is not null && memberInfo.Module.Assembly.ReflectionOnly;

private static bool IsTypeInheriting(Type? type1, Type type2)
{
while (type1 is not null)
{
if (type1.AssemblyQualifiedName.Equals(type2.AssemblyQualifiedName, StringComparison.Ordinal))
{
return true;
}

type1 = type1.BaseType;
}

return false;
}
#endif
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

#if !WINDOWS_UWP
using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers;
using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Deployment;
#endif
using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface;
Expand Down Expand Up @@ -38,7 +39,7 @@ internal sealed class TestDeployment : ITestDeployment
/// Initializes a new instance of the <see cref="TestDeployment"/> class.
/// </summary>
public TestDeployment()
: this(new DeploymentItemUtility(new ReflectionUtility()), new DeploymentUtility(), new FileUtility())
: this(new DeploymentItemUtility(ReflectHelper.Instance), new DeploymentUtility(), new FileUtility())
{
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

#if !WINDOWS_UWP

using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers;
using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Deployment;
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
using Microsoft.VisualStudio.TestTools.UnitTesting;
Expand All @@ -14,8 +15,7 @@ namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Uti
/// </summary>
internal sealed class DeploymentItemUtility
{
// REVIEW: it would be better if this was a ReflectionHelper, because helper is able to cache. But we don't have reflection helper here, because this is platform services dll.
private readonly ReflectionUtility _reflectionUtility;
private readonly ReflectHelper _reflectHelper;

/// <summary>
/// A cache for class level deployment items.
Expand All @@ -25,10 +25,10 @@ internal sealed class DeploymentItemUtility
/// <summary>
/// Initializes a new instance of the <see cref="DeploymentItemUtility"/> class.
/// </summary>
/// <param name="reflectionUtility"> The reflect helper. </param>
internal DeploymentItemUtility(ReflectionUtility reflectionUtility)
/// <param name="reflectHelper"> The reflect helper. </param>
internal DeploymentItemUtility(ReflectHelper reflectHelper)
{
_reflectionUtility = reflectionUtility;
_reflectHelper = reflectHelper;
_classLevelDeploymentItems = [];
}

Expand All @@ -42,9 +42,7 @@ internal IList<DeploymentItem> GetClassLevelDeploymentItems(Type type, ICollecti
{
if (!_classLevelDeploymentItems.TryGetValue(type, out IList<DeploymentItem>? value))
{
IReadOnlyList<object> deploymentItemAttributes = _reflectionUtility.GetCustomAttributes(
type,
typeof(DeploymentItemAttribute));
IEnumerable<DeploymentItemAttribute> deploymentItemAttributes = _reflectHelper.GetAttributes<DeploymentItemAttribute>(type);
value = GetDeploymentItems(deploymentItemAttributes, warnings);
_classLevelDeploymentItems[type] = value;
}
Expand All @@ -61,7 +59,8 @@ internal IList<DeploymentItem> GetClassLevelDeploymentItems(Type type, ICollecti
internal KeyValuePair<string, string>[]? GetDeploymentItems(MethodInfo method, IEnumerable<DeploymentItem> classLevelDeploymentItems,
ICollection<string> warnings)
{
List<DeploymentItem> testLevelDeploymentItems = GetDeploymentItems(_reflectionUtility.GetCustomAttributes(method, typeof(DeploymentItemAttribute)), warnings);
IEnumerable<DeploymentItemAttribute> deploymentItemAttributes = _reflectHelper.GetAttributes<DeploymentItemAttribute>(method);
List<DeploymentItem> testLevelDeploymentItems = GetDeploymentItems(deploymentItemAttributes, warnings);

return ToKeyValuePairs(Concat(testLevelDeploymentItems, classLevelDeploymentItems));
}
Expand Down Expand Up @@ -174,11 +173,11 @@ private static bool IsInvalidPath(string path)
return false;
}

private static List<DeploymentItem> GetDeploymentItems(IEnumerable deploymentItemAttributes, ICollection<string> warnings)
private static List<DeploymentItem> GetDeploymentItems(IEnumerable<DeploymentItemAttribute> deploymentItemAttributes, ICollection<string> warnings)
{
var deploymentItems = new List<DeploymentItem>();

foreach (DeploymentItemAttribute deploymentItemAttribute in deploymentItemAttributes.Cast<DeploymentItemAttribute>())
foreach (DeploymentItemAttribute deploymentItemAttribute in deploymentItemAttributes)
{
if (IsValidDeploymentItem(deploymentItemAttribute.Path, deploymentItemAttribute.OutputDirectory, out string? warning))
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

#if !WINDOWS_UWP

using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers;
using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Deployment;
using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Extensions;

Expand All @@ -24,7 +25,7 @@ internal abstract class DeploymentUtilityBase
protected const string DeploymentFolderPrefix = "Deploy";

public DeploymentUtilityBase()
: this(new DeploymentItemUtility(new ReflectionUtility()), new AssemblyUtility(), new FileUtility())
: this(new DeploymentItemUtility(ReflectHelper.Instance), new AssemblyUtility(), new FileUtility())
{
}

Expand Down
Loading
Loading