diff --git a/src/Adapter/MSTest.TestAdapter/Execution/TypeCache.cs b/src/Adapter/MSTest.TestAdapter/Execution/TypeCache.cs index 5a16b985dd..c2dc127f2d 100644 --- a/src/Adapter/MSTest.TestAdapter/Execution/TypeCache.cs +++ b/src/Adapter/MSTest.TestAdapter/Execution/TypeCache.cs @@ -2,9 +2,11 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System.Collections.Concurrent; +using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Reflection; using System.Security; +using System.Text; using Microsoft.TestPlatform.AdapterUtilities.ManagedNameUtilities; using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Discovery; @@ -152,6 +154,16 @@ public override object InitializeLifetimeService() { // Load the class type Type? type = LoadType(typeName, testMethod.AssemblyName); + + // VSTest managed feature is not working properly and ends up providing names that are not fully + // unescaped causing reflection to fail loading. For the cases we know this is happening, we will + // try to manually unescape the type name and load the type again. + if (type == null + && TryGetUnescapedManagedTypeName(testMethod, out var unescapedTypeName)) + { + type = LoadType(unescapedTypeName, testMethod.AssemblyName); + } + if (type == null) { // This means the class containing the test method could not be found. @@ -169,6 +181,50 @@ public override object InitializeLifetimeService() return classInfo; } + private static bool TryGetUnescapedManagedTypeName(TestMethod testMethod, [NotNullWhen(true)] out string? unescapedTypeName) + { + if (testMethod.Hierarchy.Count != 4) + { + unescapedTypeName = null; + return false; + } + + StringBuilder unescapedTypeNameBuilder = new(); + int i = -1; + foreach (var hierarchyPart in testMethod.Hierarchy) + { + i++; + if (i is not 1 and not 2 || hierarchyPart is null) + { + continue; + } + +#if NETCOREAPP + if (hierarchyPart.StartsWith('\'') && hierarchyPart.EndsWith('\'')) + { + unescapedTypeNameBuilder.Append(hierarchyPart.AsSpan(1, hierarchyPart.Length - 2)); + } +#else + if (hierarchyPart.StartsWith("'", StringComparison.Ordinal) && hierarchyPart.EndsWith("'", StringComparison.Ordinal)) + { + unescapedTypeNameBuilder.Append(hierarchyPart.Substring(1, hierarchyPart.Length - 2)); + } +#endif + else + { + unescapedTypeNameBuilder.Append(hierarchyPart); + } + + if (i == 1) + { + unescapedTypeNameBuilder.Append('.'); + } + } + + unescapedTypeName = unescapedTypeNameBuilder.ToString(); + return unescapedTypeName != testMethod.FullClassName; + } + /// /// Loads the parameter type from the parameter assembly. /// diff --git a/src/Adapter/MSTest.TestAdapter/Execution/UnitTestRunner.cs b/src/Adapter/MSTest.TestAdapter/Execution/UnitTestRunner.cs index b477bb6355..a22564993f 100644 --- a/src/Adapter/MSTest.TestAdapter/Execution/UnitTestRunner.cs +++ b/src/Adapter/MSTest.TestAdapter/Execution/UnitTestRunner.cs @@ -138,7 +138,6 @@ internal UnitTestResult[] RunSingleTest(TestMethod testMethod, IDictionary] -type UnitTest1 () = +type ``This is a test type`` () = [] member this.``Test method passing with a . in it`` () = - Assert.IsTrue(true); + Assert.IsTrue(true); \ No newline at end of file