Skip to content

Commit

Permalink
Workaround issue with managed type name utility (#1876)
Browse files Browse the repository at this point in the history
  • Loading branch information
Evangelink authored Dec 5, 2023
1 parent 8c9530e commit 966f585
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 6 deletions.
56 changes: 56 additions & 0 deletions src/Adapter/MSTest.TestAdapter/Execution/TypeCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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.
Expand All @@ -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;
}

/// <summary>
/// Loads the parameter type from the parameter assembly.
/// </summary>
Expand Down
10 changes: 6 additions & 4 deletions src/Adapter/MSTest.TestAdapter/Execution/UnitTestRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,6 @@ internal UnitTestResult[] RunSingleTest(TestMethod testMethod, IDictionary<strin
return notRunnableResult;
}

DebugEx.Assert(testMethodInfo is not null, "testMethodInfo should not be null.");
RunRequiredCleanups(testContext, testMethodInfo, testMethod, notRunnableResult);

return notRunnableResult;
Expand All @@ -157,18 +156,21 @@ internal UnitTestResult[] RunSingleTest(TestMethod testMethod, IDictionary<strin
}
}

private void RunRequiredCleanups(ITestContext testContext, TestMethodInfo testMethodInfo, TestMethod testMethod, UnitTestResult[] results)
private void RunRequiredCleanups(ITestContext testContext, TestMethodInfo? testMethodInfo, TestMethod testMethod, UnitTestResult[] results)
{
bool shouldRunClassCleanup = false;
bool shouldRunClassAndAssemblyCleanup = false;
_classCleanupManager?.MarkTestComplete(testMethodInfo, testMethod, out shouldRunClassCleanup, out shouldRunClassAndAssemblyCleanup);
if (testMethodInfo is not null)
{
_classCleanupManager?.MarkTestComplete(testMethodInfo, testMethod, out shouldRunClassCleanup, out shouldRunClassAndAssemblyCleanup);
}

using LogMessageListener logListener = new(MSTestSettings.CurrentSettings.CaptureDebugTraces);
try
{
if (shouldRunClassCleanup)
{
testMethodInfo.Parent.ExecuteClassCleanup();
testMethodInfo?.Parent.ExecuteClassCleanup();
}

if (shouldRunClassAndAssemblyCleanup)
Expand Down
4 changes: 2 additions & 2 deletions test/IntegrationTests/TestAssets/FSharpTestProject/Tests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ open System
open Microsoft.VisualStudio.TestTools.UnitTesting

[<TestClass>]
type UnitTest1 () =
type ``This is a test type`` () =

[<TestMethod>]
member this.``Test method passing with a . in it`` () =
Assert.IsTrue(true);
Assert.IsTrue(true);

0 comments on commit 966f585

Please sign in to comment.