diff --git a/src/TestFramework/TestFramework/Attributes/DataSource/DynamicDataAttribute.cs b/src/TestFramework/TestFramework/Attributes/DataSource/DynamicDataAttribute.cs index fc83907db3..9ac68e24b3 100644 --- a/src/TestFramework/TestFramework/Attributes/DataSource/DynamicDataAttribute.cs +++ b/src/TestFramework/TestFramework/Attributes/DataSource/DynamicDataAttribute.cs @@ -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; /// @@ -103,7 +104,7 @@ public DynamicDataAttribute(string dynamicDataSourceName, params object?[] dynam /// Specifies whether the data is stored as property, in method, or in field. /// [EditorBrowsable(EditorBrowsableState.Never)] - public DynamicDataAttribute(string dynamicDataSourceName, Type dynamicDataDeclaringType, DynamicDataSourceType dynamicDataSourceType) + public DynamicDataAttribute(string dynamicDataSourceName, [DynamicallyAccessedMembers(DynamicDataOperations.RequiredMemberTypes)] Type dynamicDataDeclaringType, DynamicDataSourceType dynamicDataSourceType) : this(dynamicDataSourceName, dynamicDataSourceType) => _dynamicDataDeclaringType = dynamicDataDeclaringType; /// @@ -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. /// - public DynamicDataAttribute(string dynamicDataSourceName, Type dynamicDataDeclaringType) + public DynamicDataAttribute(string dynamicDataSourceName, [DynamicallyAccessedMembers(DynamicDataOperations.RequiredMemberTypes)] Type dynamicDataDeclaringType) : this(dynamicDataSourceName) => _dynamicDataDeclaringType = dynamicDataDeclaringType; /// @@ -134,7 +135,7 @@ public DynamicDataAttribute(string dynamicDataSourceName, Type dynamicDataDeclar /// /// Arguments to be passed to method referred to by . /// - 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; @@ -149,6 +150,7 @@ public DynamicDataAttribute(string dynamicDataSourceName, Type dynamicDataDeclar /// /// Gets or sets the declaring type used to customize the display name in test results. /// + [DynamicallyAccessedMembers(DynamicDataOperations.RequiredMemberTypes)] public Type? DynamicDataDisplayNameDeclaringType { get; set; } /// @@ -168,7 +170,7 @@ public IEnumerable 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) diff --git a/src/TestFramework/TestFramework/Attributes/DataSource/DynamicDataOperations.cs b/src/TestFramework/TestFramework/Attributes/DataSource/DynamicDataOperations.cs index be9c634584..b5ba8ec880 100644 --- a/src/TestFramework/TestFramework/Attributes/DataSource/DynamicDataOperations.cs +++ b/src/TestFramework/TestFramework/Attributes/DataSource/DynamicDataOperations.cs @@ -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 GetData(Type? dynamicDataDeclaringType, DynamicDataSourceType dynamicDataSourceType, string dynamicDataSourceName, object?[] dynamicDataSourceArguments, MethodInfo methodInfo) + /// + /// The members that a dynamic data source type may expose. resolves the source member with + /// and , so the source can be an + /// inherited (for example ) member declared on a base type. Only + /// preserves inherited non-public members across the whole base chain + /// (the granular NonPublic* flags preserve only members declared directly on the annotated type, and the + /// NonPublic*WithInherited flags are not available on every target framework's base class library). Annotating a + /// with this value keeps the whole member surface alive under trimming and NativeAOT, so + /// can resolve the source by name at runtime. + /// + /// + /// This mirrors the [DynamicDependency(DynamicallyAccessedMemberTypes.All, ...)] that + /// MSTest.SourceGeneration 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. + /// + internal const DynamicallyAccessedMemberTypes RequiredMemberTypes = DynamicallyAccessedMemberTypes.All; + + public static IEnumerable 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; @@ -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; } diff --git a/src/TestFramework/TestFramework/InternalAPI/InternalAPI.Unshipped.txt b/src/TestFramework/TestFramework/InternalAPI/InternalAPI.Unshipped.txt index 7dc5c58110..118fea878f 100644 --- a/src/TestFramework/TestFramework/InternalAPI/InternalAPI.Unshipped.txt +++ b/src/TestFramework/TestFramework/InternalAPI/InternalAPI.Unshipped.txt @@ -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?