Skip to content
Merged
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 @@ -42,6 +42,7 @@ public sealed class DynamicDataAttribute : Attribute, ITestDataSource, ITestData
private readonly string _dynamicDataSourceName;
private readonly DynamicDataSourceType _dynamicDataSourceType;
private readonly object?[] _dynamicDataSourceArguments = [];
[DynamicallyAccessedMembers(DynamicDataOperations.RequiredMemberTypes)]
private readonly Type? _dynamicDataDeclaringType;

/// <summary>
Expand Down Expand Up @@ -103,7 +104,7 @@ public DynamicDataAttribute(string dynamicDataSourceName, params object?[] dynam
/// Specifies whether the data is stored as property, in method, or in field.
/// </param>
[EditorBrowsable(EditorBrowsableState.Never)]
public DynamicDataAttribute(string dynamicDataSourceName, Type dynamicDataDeclaringType, DynamicDataSourceType dynamicDataSourceType)
public DynamicDataAttribute(string dynamicDataSourceName, [DynamicallyAccessedMembers(DynamicDataOperations.RequiredMemberTypes)] Type dynamicDataDeclaringType, DynamicDataSourceType dynamicDataSourceType)
Comment thread
Evangelink marked this conversation as resolved.
: this(dynamicDataSourceName, dynamicDataSourceType) => _dynamicDataDeclaringType = dynamicDataDeclaringType;

/// <summary>
Expand All @@ -117,7 +118,7 @@ public DynamicDataAttribute(string dynamicDataSourceName, Type dynamicDataDeclar
/// The declaring type of property, method, or field having data. Useful in cases when declaring type is present in a class different from
/// test method's class. If null, declaring type defaults to test method's class type.
/// </param>
public DynamicDataAttribute(string dynamicDataSourceName, Type dynamicDataDeclaringType)
public DynamicDataAttribute(string dynamicDataSourceName, [DynamicallyAccessedMembers(DynamicDataOperations.RequiredMemberTypes)] Type dynamicDataDeclaringType)
: this(dynamicDataSourceName) => _dynamicDataDeclaringType = dynamicDataDeclaringType;

/// <summary>
Expand All @@ -134,7 +135,7 @@ public DynamicDataAttribute(string dynamicDataSourceName, Type dynamicDataDeclar
/// <param name="dynamicDataSourceArguments">
/// Arguments to be passed to method referred to by <paramref name="dynamicDataSourceName"/>.
/// </param>
public DynamicDataAttribute(string dynamicDataSourceName, Type dynamicDataDeclaringType, params object?[] dynamicDataSourceArguments)
public DynamicDataAttribute(string dynamicDataSourceName, [DynamicallyAccessedMembers(DynamicDataOperations.RequiredMemberTypes)] Type dynamicDataDeclaringType, params object?[] dynamicDataSourceArguments)
: this(dynamicDataSourceName)
{
_dynamicDataDeclaringType = dynamicDataDeclaringType;
Expand All @@ -149,6 +150,7 @@ public DynamicDataAttribute(string dynamicDataSourceName, Type dynamicDataDeclar
/// <summary>
/// Gets or sets the declaring type used to customize the display name in test results.
/// </summary>
[DynamicallyAccessedMembers(DynamicDataOperations.RequiredMemberTypes)]
public Type? DynamicDataDisplayNameDeclaringType { get; set; }

/// <summary>
Expand All @@ -168,7 +170,7 @@ public IEnumerable<object[]> GetData(MethodInfo methodInfo)
return TestDataSourceUtilities.ComputeDefaultDisplayName(methodInfo, data);
}

Type? dynamicDisplayNameDeclaringType = DynamicDataDisplayNameDeclaringType ?? methodInfo.DeclaringType;
Type? dynamicDisplayNameDeclaringType = DynamicDataDisplayNameDeclaringType ?? DynamicDataOperations.GetTestMethodDeclaringType(methodInfo);
DebugEx.Assert(dynamicDisplayNameDeclaringType is not null, "Declaring type of test data cannot be null.");

MethodInfo method = dynamicDisplayNameDeclaringType.GetTypeInfo().GetDeclaredMethod(DynamicDataDisplayName)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,32 @@ internal static class DynamicDataOperations
{
private const BindingFlags MemberLookup = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.FlattenHierarchy;

public static IEnumerable<object[]> GetData(Type? dynamicDataDeclaringType, DynamicDataSourceType dynamicDataSourceType, string dynamicDataSourceName, object?[] dynamicDataSourceArguments, MethodInfo methodInfo)
/// <summary>
/// The members that a dynamic data source type may expose. <see cref="MemberLookup"/> resolves the source member with
/// <see cref="BindingFlags.NonPublic"/> and <see cref="BindingFlags.FlattenHierarchy"/>, so the source can be an
/// inherited (for example <see langword="protected"/> <see langword="static"/>) member declared on a base type. Only
/// <see cref="DynamicallyAccessedMemberTypes.All"/> preserves inherited non-public members across the whole base chain
/// (the granular <c>NonPublic*</c> flags preserve only members declared directly on the annotated type, and the
/// <c>NonPublic*WithInherited</c> flags are not available on every target framework's base class library). Annotating a
/// <see cref="Type"/> with this value keeps the whole member surface alive under trimming and NativeAOT, so
/// <see cref="DynamicDataAttribute"/> can resolve the source by name at runtime.
/// </summary>
/// <remarks>
/// This mirrors the <c>[DynamicDependency(DynamicallyAccessedMemberTypes.All, ...)]</c> that
/// <c>MSTest.SourceGeneration</c> already emits for every discovered test class and its base types. It over-preserves
/// (constructors, events, instance members, and so on) because there is no static-only or member-kind-scoped variant
/// that also walks the base hierarchy; this is intentional and unavoidable to keep the inherited-source scenario
/// trim-safe.
/// </remarks>
internal const DynamicallyAccessedMemberTypes RequiredMemberTypes = DynamicallyAccessedMemberTypes.All;

public static IEnumerable<object[]> GetData([DynamicallyAccessedMembers(RequiredMemberTypes)] Type? dynamicDataDeclaringType, DynamicDataSourceType dynamicDataSourceType, string dynamicDataSourceName, object?[] dynamicDataSourceArguments, MethodInfo methodInfo)
{
// Check if the declaring type of test data is passed in. If not, default to test method's class type.
dynamicDataDeclaringType ??= methodInfo.DeclaringType;
// In the supported trimming/NativeAOT configuration the test class (and its base types) are rooted by the
// [DynamicDependency(All)] that MSTest.SourceGeneration emits, so MethodInfo.DeclaringType stays trim-safe even
// though it is not statically annotated with DynamicallyAccessedMembersAttribute (see GetTestMethodDeclaringType).
dynamicDataDeclaringType ??= GetTestMethodDeclaringType(methodInfo);
DebugEx.Assert(dynamicDataDeclaringType is not null, "Declaring type of test data cannot be null.");

object? obj = null;
Expand Down Expand Up @@ -170,4 +192,9 @@ private static bool TryGetData(object dataSource, [NotNullWhen(true)] out IEnume
data = null;
return false;
}

[return: DynamicallyAccessedMembers(RequiredMemberTypes)]
[UnconditionalSuppressMessage("Trimming", "IL2073:Value returned does not have matching annotations", Justification = "In the supported trimming/NativeAOT configuration, test classes are source-generated: MSTest.SourceGeneration emits [DynamicDependency(DynamicallyAccessedMemberTypes.All, ...)] for every discovered test class and its base types, so MethodInfo.DeclaringType and its members are already rooted. This fallback only runs for the test method's own declaring type; it does not claim safety for unsupported reflection-only trimmed callers that discover tests without the source generator.")]
internal static Type? GetTestMethodDeclaringType(MethodInfo methodInfo)
=> methodInfo.DeclaringType;
Comment thread
Evangelink marked this conversation as resolved.
}
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
#nullable enable
const Microsoft.VisualStudio.TestTools.UnitTesting.DynamicDataOperations.RequiredMemberTypes = (System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes)-1 -> System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes
static Microsoft.VisualStudio.TestTools.UnitTesting.DynamicDataOperations.GetTestMethodDeclaringType(System.Reflection.MethodInfo! methodInfo) -> System.Type?
Comment thread
Evangelink marked this conversation as resolved.
Loading