diff --git a/src/Adapter/MSTestAdapter.PlatformServices/AssemblyResolver.cs b/src/Adapter/MSTestAdapter.PlatformServices/AssemblyResolver.cs index 345e8a2a27..7f92518fde 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/AssemblyResolver.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/AssemblyResolver.cs @@ -307,15 +307,13 @@ private static /// /// The path of the assembly. /// The loaded . + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:Members attributed with RequiresUnreferencedCode may break when trimming", Justification = "AssemblyResolver is part of the legacy reflection-mode loader and is not used in source-generator / Native AOT execution mode.")] #if NETFRAMEWORK protected virtual #else private static #endif - // This whole class is not used in source generator mode. -#pragma warning disable IL2026 // Members attributed with RequiresUnreferencedCode may break when trimming Assembly LoadAssemblyFrom(string path) => Assembly.LoadFrom(path); -#pragma warning restore IL2026 // Members attributed with RequiresUnreferencedCode may break when trimming #if NETFRAMEWORK /// diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Extensions/MethodInfoExtensions.cs b/src/Adapter/MSTestAdapter.PlatformServices/Extensions/MethodInfoExtensions.cs index 99ebd885cc..aba95115cf 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Extensions/MethodInfoExtensions.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Extensions/MethodInfoExtensions.cs @@ -240,6 +240,8 @@ private static void InferGenerics(Type parameterType, Type argumentType, List<(T // // [DataRow(0, "Hello")] // public void TestMethod(T2 p0, T1, p1) { } + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2060:Call to 'System.Reflection.MethodInfo.MakeGenericMethod' can not be statically analyzed.", Justification = "Generic test methods with substituted type arguments are part of MSTest's reflection-mode adapter. Native AOT support relies on MSTest source-generated metadata, not on this code path.")] + [UnconditionalSuppressMessage("Aot", "IL3050:Avoid calling members annotated with 'RequiresDynamicCodeAttribute' when publishing as Native AOT", Justification = "Generic test methods with substituted type arguments are part of MSTest's reflection-mode adapter. Native AOT support relies on MSTest source-generated metadata, not on this code path.")] private static MethodInfo ConstructGenericMethod(MethodInfo methodInfo, object?[]? arguments) { DebugEx.Assert(methodInfo.IsGenericMethod, "ConstructGenericMethod should only be called for a generic method."); diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Helpers/DataSerializationHelper.cs b/src/Adapter/MSTestAdapter.PlatformServices/Helpers/DataSerializationHelper.cs index c7c980e9fd..b848f88591 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Helpers/DataSerializationHelper.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Helpers/DataSerializationHelper.cs @@ -12,6 +12,11 @@ namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers; internal static class DataSerializationHelper { + private const string DataContractSerializationJustification = + "Data contract serialization is used for cross-process VSTest payloads. " + + "This should be safe as long as our generator mentions getting fields / properties of the target type. " + + "https://github.com/dotnet/runtime/issues/71350#issuecomment-1168140551"; + private static readonly ConcurrentDictionary SerializerCache = new(); private static readonly DataContractJsonSerializerSettings SerializerSettings = new() { @@ -30,6 +35,8 @@ internal static class DataSerializationHelper /// /// Data array to serialize. /// Serialized array. + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:Members attributed with RequiresUnreferencedCode may break when trimming", Justification = DataContractSerializationJustification)] + [UnconditionalSuppressMessage("Aot", "IL3050:Avoid calling members annotated with 'RequiresDynamicCodeAttribute' when publishing as Native AOT", Justification = DataContractSerializationJustification)] public static string?[]? Serialize(object?[]? data) { if (data == null) @@ -61,14 +68,7 @@ internal static class DataSerializationHelper #endif using var memoryStream = new MemoryStream(); - // This should be safe as long as our generator mentions - // getting fields / properties of the target type. https://github.com/dotnet/runtime/issues/71350#issuecomment-1168140551 - // Not the best solution, maybe we can replace this with System.Text.Json, but the we need one generator calling the other. -#pragma warning disable IL3050 // IL3050: Avoid calling members annotated with 'RequiresDynamicCodeAttribute' when publishing as Native AOT -#pragma warning disable IL2026 // IL2026: Members attributed with RequiresUnreferencedCode may break when trimming serializer.WriteObject(memoryStream, data[i]); -#pragma warning restore IL3050 // IL3050: Avoid calling members annotated with 'RequiresDynamicCodeAttribute' when publishing as Native AOT -#pragma warning restore IL2026 // IL2026: Members attributed with RequiresUnreferencedCode may break when trimming byte[] serializerData = memoryStream.ToArray(); serializedData[dataIndex] = Encoding.UTF8.GetString(serializerData, 0, serializerData.Length); @@ -82,6 +82,8 @@ internal static class DataSerializationHelper /// /// Serialized data array to deserialize. /// Deserialized array. + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:Members attributed with RequiresUnreferencedCode may break when trimming", Justification = DataContractSerializationJustification)] + [UnconditionalSuppressMessage("Aot", "IL3050:Avoid calling members annotated with 'RequiresDynamicCodeAttribute' when publishing as Native AOT", Justification = DataContractSerializationJustification)] public static object?[]? Deserialize(string?[]? serializedData) { if (serializedData == null || serializedData.Length % 2 != 0) @@ -111,14 +113,7 @@ internal static class DataSerializationHelper byte[] serializedDataBytes = Encoding.UTF8.GetBytes(serializedValue); using var memoryStream = new MemoryStream(serializedDataBytes); - // This should be safe as long as our generator mentions - // getting fields / properties of the target type. https://github.com/dotnet/runtime/issues/71350#issuecomment-1168140551 - // Not the best solution, maybe we can replace this with System.Text.Json, but the we need one generator calling the other. -#pragma warning disable IL3050 // IL3050: Avoid calling members annotated with 'RequiresDynamicCodeAttribute' when publishing as Native AOT -#pragma warning disable IL2026 // IL2026: Members attributed with RequiresUnreferencedCode may break when trimming data[i] = serializer.ReadObject(memoryStream); -#pragma warning restore IL3050 // IL3050: Avoid calling members annotated with 'RequiresDynamicCodeAttribute' when publishing as Native AOT -#pragma warning restore IL2026 // IL2026: Members attributed with RequiresUnreferencedCode may break when trimming // For some reason, we don't get SerializationSurrogateProvider.GetDeserializedObject to be called by .NET runtime. // So we manually call it. data[i] = SerializationSurrogateProvider.GetDeserializedObject(data[i]!); @@ -128,26 +123,20 @@ internal static class DataSerializationHelper } private static DataContractJsonSerializer GetSerializer(string assemblyQualifiedName) - => SerializerCache.GetOrAdd( - assemblyQualifiedName, - // This should be safe as long as our generator mentions - // getting fields / properties of the target type. https://github.com/dotnet/runtime/issues/71350#issuecomment-1168140551 - // Not the best solution, maybe we can replace this with System.Text.Json, but the we need one generator calling the other. -#pragma warning disable IL3050 // IL3050: Avoid calling members annotated with 'RequiresDynamicCodeAttribute' when publishing as Native AOT -#pragma warning disable IL2026 // IL2026: Members attributed with RequiresUnreferencedCode may break when trimming - _ => new DataContractJsonSerializer(PlatformServiceProvider.Instance.ReflectionOperations.GetType(assemblyQualifiedName) ?? typeof(object), SerializerSettings)); -#pragma warning restore IL3050 // IL3050: Avoid calling members annotated with 'RequiresDynamicCodeAttribute' when publishing as Native AOT -#pragma warning restore IL2026 // IL2026: Members attributed with RequiresUnreferencedCode may break when trimming + => SerializerCache.GetOrAdd(assemblyQualifiedName, CreateSerializerForAssemblyQualifiedName); private static DataContractJsonSerializer GetSerializer(Type type) - => SerializerCache.GetOrAdd( - type.AssemblyQualifiedName!, - // This should be safe as long as our generator mentions - // getting fields / properties of the target type. https://github.com/dotnet/runtime/issues/71350#issuecomment-1168140551 - // Not the best solution, maybe we can replace this with System.Text.Json, but the we need one generator calling the other. -#pragma warning disable IL3050 // IL3050: Avoid calling members annotated with 'RequiresDynamicCodeAttribute' when publishing as Native AOT -#pragma warning disable IL2026 // IL2026: Members attributed with RequiresUnreferencedCode may break when trimming - _ => new DataContractJsonSerializer(type, SerializerSettings)); + => SerializerCache.GetOrAdd(type.AssemblyQualifiedName!, _ => CreateSerializerForType(type)); + + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:Members attributed with RequiresUnreferencedCode may break when trimming", Justification = DataContractSerializationJustification)] + [UnconditionalSuppressMessage("Aot", "IL3050:Avoid calling members annotated with 'RequiresDynamicCodeAttribute' when publishing as Native AOT", Justification = DataContractSerializationJustification)] + private static DataContractJsonSerializer CreateSerializerForAssemblyQualifiedName(string assemblyQualifiedName) + => new(PlatformServiceProvider.Instance.ReflectionOperations.GetType(assemblyQualifiedName) ?? typeof(object), SerializerSettings); + + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:Members attributed with RequiresUnreferencedCode may break when trimming", Justification = DataContractSerializationJustification)] + [UnconditionalSuppressMessage("Aot", "IL3050:Avoid calling members annotated with 'RequiresDynamicCodeAttribute' when publishing as Native AOT", Justification = DataContractSerializationJustification)] + private static DataContractJsonSerializer CreateSerializerForType(Type type) + => new(type, SerializerSettings); [DataContract] private sealed class SurrogatedDateOnly @@ -235,6 +224,4 @@ public Type GetSurrogateType(Type type) return type; } } -#pragma warning restore IL3050 // IL3050: Avoid calling members annotated with 'RequiresDynamicCodeAttribute' when publishing as Native AOT -#pragma warning restore IL2026 // IL2026: Members attributed with RequiresUnreferencedCode may break when trimming } diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Helpers/ManagedNameHelper.cs b/src/Adapter/MSTestAdapter.PlatformServices/Helpers/ManagedNameHelper.cs index e60eb80e27..8c871c4c7c 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Helpers/ManagedNameHelper.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Helpers/ManagedNameHelper.cs @@ -112,6 +112,7 @@ public static void GetManagedNameAndHierarchy(MethodBase method, out string mana /// More information about and can be found in /// the RFC. /// + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:Members attributed with RequiresUnreferencedCode may break when trimming", Justification = "Reflection-based name resolution is used by the legacy VSTest bridge for managed-name lookup. Native AOT support relies on MSTest source-generated reflection metadata, not on this code path.")] public static MethodInfo GetMethod(Assembly assembly, string managedTypeName, string managedMethodName) { Type type = assembly.GetType(managedTypeName, throwOnError: false, ignoreCase: false) @@ -128,6 +129,7 @@ public static MethodInfo GetMethod(Assembly assembly, string managedTypeName, st return method ?? throw new InvalidManagedNameException(); } + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2070:'this' argument does not satisfy 'DynamicallyAccessedMembersAttribute' in call to target method.", Justification = "Reflection-based name resolution is used by the legacy VSTest bridge for managed-name lookup. Native AOT support relies on MSTest source-generated reflection metadata, not on this code path.")] private static MethodInfo? FindMethod(Type type, string methodName, int methodArity, string[]? parameterTypes) { bool Filter(MemberInfo mbr, object? param) diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Services/ReflectionOperations.cs b/src/Adapter/MSTestAdapter.PlatformServices/Services/ReflectionOperations.cs index 4cb8ce6729..c9745dd68f 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Services/ReflectionOperations.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Services/ReflectionOperations.cs @@ -48,47 +48,50 @@ internal sealed class ReflectionOperations : MarshalByRefObject, IReflectionOper public object[] GetCustomAttributes(Assembly assembly, Type type) => assembly.GetCustomAttributes(type, inherit: true); -#pragma warning disable IL2070 // this' argument does not satisfy 'DynamicallyAccessedMembersAttribute' in call to 'target method'. -#pragma warning disable IL2026 // Members attributed with RequiresUnreferencedCode may break when trimming -#pragma warning disable IL2067 // 'target parameter' argument does not satisfy 'DynamicallyAccessedMembersAttribute' in call to 'target method'. -#pragma warning disable IL2057 // Unrecognized value passed to the typeName parameter of 'System.Type.GetType(String)' + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2070:'this' argument does not satisfy 'DynamicallyAccessedMembersAttribute' in call to target method.", Justification = "Reflection-based discovery/execution is the MSTest reflection-mode adapter path. Native AOT support relies on MSTest source-generated reflection metadata, not on this code path.")] public ConstructorInfo[] GetDeclaredConstructors(Type classType) => classType.GetConstructors(DeclaredOnlyLookup); + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2070:'this' argument does not satisfy 'DynamicallyAccessedMembersAttribute' in call to target method.", Justification = "Reflection-based discovery/execution is the MSTest reflection-mode adapter path. Native AOT support relies on MSTest source-generated reflection metadata, not on this code path.")] public MethodInfo[] GetDeclaredMethods(Type classType) => classType.GetMethods(DeclaredOnlyLookup); + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2070:'this' argument does not satisfy 'DynamicallyAccessedMembersAttribute' in call to target method.", Justification = "Reflection-based discovery/execution is the MSTest reflection-mode adapter path. Native AOT support relies on MSTest source-generated reflection metadata, not on this code path.")] public PropertyInfo[] GetDeclaredProperties(Type type) => type.GetProperties(DeclaredOnlyLookup); + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:Members attributed with RequiresUnreferencedCode may break when trimming", Justification = "Reflection-based discovery/execution is the MSTest reflection-mode adapter path. Native AOT support relies on MSTest source-generated reflection metadata, not on this code path.")] public Type[] GetDefinedTypes(Assembly assembly) => assembly.GetTypes(); + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2070:'this' argument does not satisfy 'DynamicallyAccessedMembersAttribute' in call to target method.", Justification = "Reflection-based discovery/execution is the MSTest reflection-mode adapter path. Native AOT support relies on MSTest source-generated reflection metadata, not on this code path.")] public MethodInfo[] GetRuntimeMethods(Type type) => type.GetMethods(Everything); + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2070:'this' argument does not satisfy 'DynamicallyAccessedMembersAttribute' in call to target method.", Justification = "Reflection-based discovery/execution is the MSTest reflection-mode adapter path. Native AOT support relies on MSTest source-generated reflection metadata, not on this code path.")] + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2067:'target parameter' argument does not satisfy 'DynamicallyAccessedMembersAttribute' in call to target method.", Justification = "Reflection-based discovery/execution is the MSTest reflection-mode adapter path. Native AOT support relies on MSTest source-generated reflection metadata, not on this code path.")] public MethodInfo? GetRuntimeMethod(Type declaringType, string methodName, Type[] parameters, bool includeNonPublic) => includeNonPublic ? declaringType.GetMethod(methodName, Everything, null, parameters, null) : declaringType.GetMethod(methodName, parameters); + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2070:'this' argument does not satisfy 'DynamicallyAccessedMembersAttribute' in call to target method.", Justification = "Reflection-based discovery/execution is the MSTest reflection-mode adapter path. Native AOT support relies on MSTest source-generated reflection metadata, not on this code path.")] public PropertyInfo? GetRuntimeProperty(Type classType, string testContextPropertyName, bool includeNonPublic) => includeNonPublic ? classType.GetProperty(testContextPropertyName, Everything) : classType.GetProperty(testContextPropertyName); + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2057:Unrecognized value passed to the typeName parameter of 'System.Type.GetType(String)'.", Justification = "Reflection-based discovery/execution is the MSTest reflection-mode adapter path. Native AOT support relies on MSTest source-generated reflection metadata, not on this code path.")] public Type? GetType(string typeName) => Type.GetType(typeName); + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:Members attributed with RequiresUnreferencedCode may break when trimming", Justification = "Reflection-based discovery/execution is the MSTest reflection-mode adapter path. Native AOT support relies on MSTest source-generated reflection metadata, not on this code path.")] public Type? GetType(Assembly assembly, string typeName) => assembly.GetType(typeName); + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2067:'target parameter' argument does not satisfy 'DynamicallyAccessedMembersAttribute' in call to target method.", Justification = "Reflection-based discovery/execution is the MSTest reflection-mode adapter path. Native AOT support relies on MSTest source-generated reflection metadata, not on this code path.")] public object? CreateInstance(Type type, object?[] parameters) => Activator.CreateInstance(type, parameters); -#pragma warning restore IL2070 // this' argument does not satisfy 'DynamicallyAccessedMembersAttribute' in call to 'target method'. -#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)' /// /// Checks to see if a member or type is decorated with the given attribute, or an attribute that derives from it. e.g. [MyTestClass] from [TestClass] will match if you look for [TestClass]. The inherit parameter does not impact this checking. diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Services/TestSourceHost.cs b/src/Adapter/MSTestAdapter.PlatformServices/Services/TestSourceHost.cs index 0e8c0c8be6..60f818f82f 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Services/TestSourceHost.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Services/TestSourceHost.cs @@ -350,6 +350,7 @@ internal string GetTargetFrameworkVersionString(string sourceFileName) /// /// A list of path. /// + [UnconditionalSuppressMessage("SingleFile", "IL3000:Avoid accessing Assembly file path when publishing as a single file", Justification = "Assembly.Location is explicitly checked for empty before use; in single-file/Native AOT scenarios it returns empty and the affected blocks are skipped.")] internal virtual List GetResolutionPaths(string sourceFileName, bool isPortableMode) { List resolutionPaths = @@ -380,7 +381,6 @@ internal virtual List GetResolutionPaths(string sourceFileName, bool isP // We check for the empty path, and in single file mode, or on source gen mode we don't allow // loading dependencies than from the current folder, which is what the default loader handles by itself. -#pragma warning disable IL3000 // Avoid accessing Assembly file path when publishing as a single file if (!string.IsNullOrEmpty(typeof(TestSourceHost).Assembly.Location)) { // Adding adapter folder to resolution paths @@ -398,7 +398,6 @@ internal virtual List GetResolutionPaths(string sourceFileName, bool isP resolutionPaths.Add(Path.GetDirectoryName(typeof(AssemblyHelper).Assembly.Location)!); } } -#pragma warning restore IL3000 // Avoid accessing Assembly file path when publishing as a single file return resolutionPaths; } diff --git a/src/Adapter/MSTestAdapter.PlatformServices/TestMethodFilter.cs b/src/Adapter/MSTestAdapter.PlatformServices/TestMethodFilter.cs index 33f554a96c..481220ad0c 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/TestMethodFilter.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/TestMethodFilter.cs @@ -117,6 +117,7 @@ internal TestProperty PropertyProvider(string propertyName) /// Discovery context. /// The logger to log exception messages too. /// Filter expression. + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2072:'target parameter' argument does not satisfy 'DynamicallyAccessedMembersAttribute' in call to target method.", Justification = "GetTestCaseFilter is part of the VSTest discovery contract on the concrete DiscoveryContext type; the runtime guarantees the method exists on supported hosts.")] private ITestCaseFilterExpression? GetTestCaseFilterFromDiscoveryContext(IDiscoveryContext context, IMessageLogger logger) { try diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Utilities/DeploymentUtilityBase.cs b/src/Adapter/MSTestAdapter.PlatformServices/Utilities/DeploymentUtilityBase.cs index f86755558b..dd6918a934 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Utilities/DeploymentUtilityBase.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Utilities/DeploymentUtilityBase.cs @@ -136,6 +136,7 @@ public static string GetTestResultsDirectory(IRunContext? runContext) => !String /// The deployment directory. /// Root results directory. /// Returns a list of deployment warnings. + [UnconditionalSuppressMessage("SingleFile", "IL3000:Avoid accessing Assembly file path when publishing as a single file", Justification = "Deployment is a reflection-mode/legacy adapter feature; in single-file/Native AOT scenarios Assembly.Location returns an empty string and the comparison falls through without error.")] protected IEnumerable Deploy(IList deploymentItems, string testSourceHandler, string deploymentDirectory, string resultsDirectory) { Ensure.NotNullOrWhiteSpace(deploymentDirectory); @@ -194,9 +195,7 @@ protected IEnumerable Deploy(IList deploymentItems, stri // Ignore the test platform files. string tempFile = Path.GetFileName(fileToDeploy); // We throw when we run in source gen mode. -#pragma warning disable IL3000 // Avoid accessing Assembly file path when publishing as a single file string assemblyName = Path.GetFileName(GetType().Assembly.Location); -#pragma warning restore IL3000 // Avoid accessing Assembly file path when publishing as a single file if (tempFile.Equals(assemblyName, StringComparison.OrdinalIgnoreCase)) { continue; diff --git a/src/Platform/Microsoft.Testing.Extensions.VSTestBridge/SynchronizedSingleSessionVSTestAndTestAnywhereAdapter.cs b/src/Platform/Microsoft.Testing.Extensions.VSTestBridge/SynchronizedSingleSessionVSTestAndTestAnywhereAdapter.cs index e2d903229e..977788a3d8 100644 --- a/src/Platform/Microsoft.Testing.Extensions.VSTestBridge/SynchronizedSingleSessionVSTestAndTestAnywhereAdapter.cs +++ b/src/Platform/Microsoft.Testing.Extensions.VSTestBridge/SynchronizedSingleSessionVSTestAndTestAnywhereAdapter.cs @@ -149,9 +149,7 @@ protected sealed override Task ExecuteRequestAsync(TestExecutionRequest request, CancellationToken cancellationToken) => ExecuteRequestWithRequestCountGuardAsync(async () => { -#pragma warning disable IL3000 // Avoid accessing Assembly file path when publishing as a single file string[] testAssemblyPaths = [.. _getTestAssemblies().Select(GetAssemblyPath)]; -#pragma warning restore IL3000 // Avoid accessing Assembly file path when publishing as a single file switch (request) { case DiscoverTestExecutionRequest discoverRequest: @@ -184,12 +182,10 @@ public void Dispose() /// returns an empty string (e.g. on Android CoreCLR /// where assemblies are memory-mapped). /// -#pragma warning disable IL3000 // Avoid accessing Assembly file path when publishing as a single file + [UnconditionalSuppressMessage("SingleFile", "IL3000:Avoid accessing Assembly file path when publishing as a single file", Justification = "Empty Assembly.Location is handled explicitly by falling back to the assembly simple name; see method body.")] internal static string GetAssemblyPath(Assembly assembly) { -#pragma warning disable IL3000 // Avoid accessing Assembly file path when publishing as a single file string location = assembly.Location; -#pragma warning restore IL3000 // Avoid accessing Assembly file path when publishing as a single file if (!string.IsNullOrEmpty(location)) { return location; @@ -204,7 +200,6 @@ internal static string GetAssemblyPath(Assembly assembly) return name + ".dll"; } -#pragma warning restore IL3000 // Avoid accessing Assembly file path when publishing as a single file private async Task ExecuteRequestWithRequestCountGuardAsync(Func asyncFunc) { diff --git a/src/TestFramework/TestFramework/Internal/ReflectionTestMethodInfo.cs b/src/TestFramework/TestFramework/Internal/ReflectionTestMethodInfo.cs index 6eee7ba64e..8d2ffd3632 100644 --- a/src/TestFramework/TestFramework/Internal/ReflectionTestMethodInfo.cs +++ b/src/TestFramework/TestFramework/Internal/ReflectionTestMethodInfo.cs @@ -47,6 +47,12 @@ public ReflectionTestMethodInfo(MethodInfo methodInfo, string? displayName) public override bool IsDefined(Type attributeType, bool inherit) => _methodInfo.IsDefined(attributeType, inherit); +#if NET5_0_OR_GREATER + [RequiresUnreferencedCode("The native code for the generic method instantiation might not be available at runtime.")] +#endif +#if NET7_0_OR_GREATER + [RequiresDynamicCode("The native code for the generic method instantiation might not be available at runtime.")] +#endif public override MethodInfo MakeGenericMethod(params Type[] typeArguments) => new ReflectionTestMethodInfo(_methodInfo.MakeGenericMethod(typeArguments), DisplayName); public override Type[] GetGenericArguments() => _methodInfo.GetGenericArguments(); diff --git a/test/IntegrationTests/MSTest.Acceptance.IntegrationTests/TrimTests.cs b/test/IntegrationTests/MSTest.Acceptance.IntegrationTests/TrimTests.cs index 7fe25eae29..cfd469dded 100644 --- a/test/IntegrationTests/MSTest.Acceptance.IntegrationTests/TrimTests.cs +++ b/test/IntegrationTests/MSTest.Acceptance.IntegrationTests/TrimTests.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using Microsoft.Testing.Platform.Acceptance.IntegrationTests; +using Microsoft.Testing.Platform.Acceptance.IntegrationTests.Helpers; namespace MSTest.Acceptance.IntegrationTests; @@ -61,5 +62,109 @@ await DotnetCli.RunAsync( cancellationToken: TestContext.CancellationToken); } + // Source code for a project that references MSTest.TestAdapter (the reflection-mode adapter) + // and runs trim analysis with TrimmerRootAssembly to scan the full surface of the assemblies + // we own. Used by Publish_WithTestAdapter_DoesNotSurfaceWarningsFromSuppressedSources. + // + // Note: We do not enable TreatWarningsAsErrors here because the reflection-mode adapter still + // depends on the vstest Microsoft.TestPlatform.ObjectModel submodule and on + // System.Private.DataContractSerialization internals, both of which emit trim warnings that + // are outside this repo's control. The test instead asserts that specific source files we + // suppressed in this repo no longer appear in publish output. + private const string TrimAnalysisWithTestAdapterSourceCode = """ +#file MSTestTrimAnalysisWithTestAdapter.csproj + + + $TargetFramework$ + Exe + true + true + + false + + + + + + + + + + + + + + +#file UnitTest1.cs +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace MyTests; + +[TestClass] +public class UnitTest1 +{ + [TestMethod] + public void TestMethod1() + { + } +} +"""; + + [TestMethod] + [DynamicData(nameof(TargetFrameworks.NetForDynamicData), typeof(TargetFrameworks))] + public async Task Publish_WithTestAdapter_DoesNotSurfaceWarningsFromSuppressedSources(string tfm) + { + // Regression test for the suppressions added in https://github.com/microsoft/testfx/pull/8686. + // + // Before that PR, the listed source files emitted trim warnings (IL20xx/IL30xx) when a + // downstream consumer published a trimmed project that referenced MSTest.TestAdapter. + // The original C# `#pragma warning disable ILxxxx` directives in those files only + // silenced the compile-time warning during MSTest's own build; they did NOT propagate to + // the IL, so the linker still emitted the warnings at the consumer's publish time. + // + // After converting those pragmas to [UnconditionalSuppressMessage] / [RequiresUnreferencedCode] + // / [RequiresDynamicCode] attributes, the suppressions are honored by the IL trimmer and + // these source-file references should no longer appear in publish output. + using TestAsset generator = await TestAsset.GenerateAssetAsync( + $"MSTestTrimAnalysisWithTestAdapter_{tfm}", + TrimAnalysisWithTestAdapterSourceCode + .PatchCodeWithReplace("$MicrosoftTestingPlatformVersion$", MicrosoftTestingPlatformVersion) + .PatchCodeWithReplace("$MSTestVersion$", MSTestVersion) + .PatchCodeWithReplace("$TargetFramework$", tfm), + addPublicFeeds: true); + + // Do NOT pass warnAsError: true here. The reflection-mode adapter still depends on the + // vstest Microsoft.TestPlatform.ObjectModel submodule and on System.Private.DataContractSerialization + // internals, both of which emit trim warnings that are outside this repo's control. Promoting them + // to errors would fail the publish with NETSDK1144 before we get a chance to inspect the warning list. + DotnetMuxerResult result = await DotnetCli.RunAsync( + $"publish {generator.TargetAssetPath} -r {RID} -f {tfm}", + warnAsError: false, + cancellationToken: TestContext.CancellationToken); + + // Files in MSTest's own source whose trim warnings are suppressed by this PR. + // The trimmer includes source file paths in its IL2xxx/IL3xxx warnings, so the absence + // of these file names in publish output is evidence that the suppression attributes work. + string[] suppressedSourceFiles = + [ + "TestSourceHost.cs", + "DeploymentUtilityBase.cs", + "ReflectionOperations.cs", + "AssemblyResolver.cs", + "DataSerializationHelper.cs", + "ManagedNameHelper.cs", + "MethodInfoExtensions.cs", + "TestMethodFilter.cs", + "SynchronizedSingleSessionVSTestAndTestAnywhereAdapter.cs", + "ReflectionTestMethodInfo.cs", + ]; + + foreach (string fileName in suppressedSourceFiles) + { + result.AssertOutputDoesNotContain(fileName); + } + } + public TestContext TestContext { get; set; } }